Fix backend lint warnings

This commit is contained in:
Felipe Knorr Kuhn 2026-01-04 13:03:42 -08:00
parent 239cc99d7e
commit 9ecd7dc2ce
No known key found for this signature in database
GPG key ID: 79619B52BB097C1A
59 changed files with 402 additions and 402 deletions

View file

@ -1,24 +1,24 @@
import type { Config } from "@jest/types" import type { Config } from '@jest/types';
const config: Config.InitialOptions = { const config: Config.InitialOptions = {
preset: "ts-jest", preset: 'ts-jest',
testEnvironment: "node", testEnvironment: 'node',
verbose: true, verbose: true,
automock: false, automock: false,
collectCoverage: true, collectCoverage: true,
collectCoverageFrom: ["./src/**/**.ts"], collectCoverageFrom: ['./src/**/**.ts'],
coverageProvider: "v8", coverageProvider: 'v8',
coverageThreshold: { coverageThreshold: {
global: { global: {
lines: 1 lines: 1
} }
}, },
setupFiles: [ setupFiles: [
"./testSetup.ts", './testSetup.ts',
], ],
testPathIgnorePatterns: [ testPathIgnorePatterns: [
"/node_modules/", '/node_modules/',
"/__integration_tests__/", '/__integration_tests__/',
], ],
} };
export default config; export default config;

View file

@ -1,21 +1,21 @@
import type { Config } from "@jest/types" import type { Config } from '@jest/types';
const config: Config.InitialOptions = { const config: Config.InitialOptions = {
preset: "ts-jest", preset: 'ts-jest',
testEnvironment: "node", testEnvironment: 'node',
verbose: true, verbose: true,
automock: false, automock: false,
collectCoverage: false, collectCoverage: false,
coverageProvider: "v8", coverageProvider: 'v8',
testMatch: [ testMatch: [
"**/__integration_tests__/**/*.test.ts" '**/__integration_tests__/**/*.test.ts'
], ],
globalSetup: "./jest.integration.setup.ts", // Start database before all tests globalSetup: './jest.integration.setup.ts', // Start database before all tests
setupFiles: [ setupFiles: [
"./testSetup.integration.ts", './testSetup.integration.ts',
], ],
globalTeardown: "./jest.integration.teardown.ts", // Stop database after all tests globalTeardown: './jest.integration.teardown.ts', // Stop database after all tests
maxWorkers: 1, // Force sequential execution maxWorkers: 1, // Force sequential execution
} };
export default config; export default config;

View file

@ -35,18 +35,18 @@ module.exports = async () => {
try { try {
const composeFile = path.join(__dirname, 'docker-compose.test.yml'); const composeFile = path.join(__dirname, 'docker-compose.test.yml');
const dockerComposeCmd = getDockerComposeCmd(); const dockerComposeCmd = getDockerComposeCmd();
// Start the container // Start the container
execSync(`${dockerComposeCmd} -f "${composeFile}" up -d`, { execSync(`${dockerComposeCmd} -f "${composeFile}" up -d`, {
stdio: 'inherit', stdio: 'inherit',
cwd: __dirname cwd: __dirname
}); });
// Wait for database to be ready // Wait for database to be ready
console.log('Waiting for database to be ready...'); console.log('Waiting for database to be ready...');
let attempts = 0; let attempts = 0;
const maxAttempts = 30; const maxAttempts = 30;
while (attempts < maxAttempts) { while (attempts < maxAttempts) {
try { try {
execSync(`${dockerComposeCmd} -f "${composeFile}" exec -T db-test mysqladmin ping -h localhost -u mempool_test -pmempool_test --silent`, { execSync(`${dockerComposeCmd} -f "${composeFile}" exec -T db-test mysqladmin ping -h localhost -u mempool_test -pmempool_test --silent`, {

View file

@ -43,7 +43,7 @@ module.exports = async () => {
]; ];
await DB.query('SET FOREIGN_KEY_CHECKS = 0'); await DB.query('SET FOREIGN_KEY_CHECKS = 0');
for (const table of tables) { for (const table of tables) {
try { try {
// Use 'silent' error logging to avoid noise for optional tables that don't exist // Use 'silent' error logging to avoid noise for optional tables that don't exist
@ -52,29 +52,29 @@ module.exports = async () => {
// Table might not exist - silently ignore // Table might not exist - silently ignore
} }
} }
await DB.query('SET FOREIGN_KEY_CHECKS = 1'); await DB.query('SET FOREIGN_KEY_CHECKS = 1');
logger.info('Integration tests cleanup completed'); logger.info('Integration tests cleanup completed');
// Close the database connection pool to prevent Jest from hanging // Close the database connection pool to prevent Jest from hanging
await DB.close(); await DB.close();
logger.info('Database connection pool closed'); logger.info('Database connection pool closed');
// Clean up singleton resources that have timers or sockets // Clean up singleton resources that have timers or sockets
mempool.destroy(); mempool.destroy();
logger.info('Mempool resources cleaned up'); logger.info('Mempool resources cleaned up');
// Close logger's UDP socket last (after all logging is done) // Close logger's UDP socket last (after all logging is done)
logger.close(); logger.close();
// Stop and remove the Docker test database container // Stop and remove the Docker test database container
// Skip if SKIP_DB_TEARDOWN is set (e.g., when test-with-db.sh manages the database) // Skip if SKIP_DB_TEARDOWN is set (e.g., when test-with-db.sh manages the database)
if (!process.env.SKIP_DB_TEARDOWN) { if (!process.env.SKIP_DB_TEARDOWN) {
try { try {
const composeFile = path.join(__dirname, 'docker-compose.test.yml'); const composeFile = path.join(__dirname, 'docker-compose.test.yml');
const dockerComposeCmd = getDockerComposeCmd(); const dockerComposeCmd = getDockerComposeCmd();
execSync(`${dockerComposeCmd} -f "${composeFile}" down -v`, { execSync(`${dockerComposeCmd} -f "${composeFile}" down -v`, {
stdio: 'inherit', stdio: 'inherit',
cwd: __dirname cwd: __dirname
}); });

View file

@ -40,7 +40,7 @@ describe('BlocksRepository Integration Tests', () => {
}); });
const block = await BlocksRepository.$getBlockByHeight(height); const block = await BlocksRepository.$getBlockByHeight(height);
expect(block).toBeDefined(); expect(block).toBeDefined();
expect(block!.height).toBe(height); expect(block!.height).toBe(height);
expect(block!.id).toBe(blockHash); expect(block!.id).toBe(blockHash);
@ -58,7 +58,7 @@ describe('BlocksRepository Integration Tests', () => {
}); });
const block = await BlocksRepository.$getBlockByHash(blockHash); const block = await BlocksRepository.$getBlockByHash(blockHash);
expect(block).toBeDefined(); expect(block).toBeDefined();
expect(block!.id).toBe(blockHash); expect(block!.id).toBe(blockHash);
expect(block!.height).toBe(height); expect(block!.height).toBe(height);
@ -71,36 +71,36 @@ describe('BlocksRepository Integration Tests', () => {
test('should check for missing blocks in range', async () => { test('should check for missing blocks in range', async () => {
// Insert blocks with a gap // Insert blocks with a gap
await insertTestBlock({ await insertTestBlock({
height: 800100, height: 800100,
hash: '0000000000000000000100000000000000000000000000000000000000000001', hash: '0000000000000000000100000000000000000000000000000000000000000001',
poolId: defaultPoolId poolId: defaultPoolId
}); });
await insertTestBlock({ await insertTestBlock({
height: 800102, height: 800102,
hash: '0000000000000000000100000000000000000000000000000000000000000003', hash: '0000000000000000000100000000000000000000000000000000000000000003',
poolId: defaultPoolId poolId: defaultPoolId
}); });
const missingBlocks = await BlocksRepository.$getMissingBlocksBetweenHeights(800100, 800102); const missingBlocks = await BlocksRepository.$getMissingBlocksBetweenHeights(800100, 800102);
expect(missingBlocks).toContain(800101); expect(missingBlocks).toContain(800101);
}); });
test('should get latest block height', async () => { test('should get latest block height', async () => {
await insertTestBlock({ await insertTestBlock({
height: 800200, height: 800200,
hash: '0000000000000000000200000000000000000000000000000000000000000001', hash: '0000000000000000000200000000000000000000000000000000000000000001',
poolId: defaultPoolId poolId: defaultPoolId
}); });
await insertTestBlock({ await insertTestBlock({
height: 800201, height: 800201,
hash: '0000000000000000000200000000000000000000000000000000000000000002', hash: '0000000000000000000200000000000000000000000000000000000000000002',
poolId: defaultPoolId poolId: defaultPoolId
}); });
const height = await BlocksRepository.$mostRecentBlockHeight(); const height = await BlocksRepository.$mostRecentBlockHeight();
expect(height).toBe(800201); expect(height).toBe(800201);
}); });
@ -121,7 +121,7 @@ describe('BlocksRepository Integration Tests', () => {
}); });
const block = await BlocksRepository.$getBlockByHash(blockHash); const block = await BlocksRepository.$getBlockByHash(blockHash);
expect(block).toBeDefined(); expect(block).toBeDefined();
expect(block).not.toBeNull(); expect(block).not.toBeNull();
// The pool should be populated with the test pool's data // The pool should be populated with the test pool's data

View file

@ -18,7 +18,7 @@ describe('Database Migration Integration Tests', () => {
}); });
test('should have schema version in state table', async () => { test('should have schema version in state table', async () => {
const [result] = await DB.query<any>("SELECT number FROM state WHERE name = 'schema_version'"); const [result] = await DB.query<any>('SELECT number FROM state WHERE name = \'schema_version\'');
expect(result).toHaveLength(1); expect(result).toHaveLength(1);
expect(result[0].number).toBeGreaterThan(0); expect(result[0].number).toBeGreaterThan(0);
}); });
@ -70,7 +70,7 @@ describe('Database Migration Integration Tests', () => {
WHERE TABLE_SCHEMA = 'mempool_test' WHERE TABLE_SCHEMA = 'mempool_test'
AND TABLE_NAME = 'blocks'` AND TABLE_NAME = 'blocks'`
); );
const columnNames = columns.map((col: any) => col.COLUMN_NAME); const columnNames = columns.map((col: any) => col.COLUMN_NAME);
expect(columnNames).toContain('height'); expect(columnNames).toContain('height');
expect(columnNames).toContain('hash'); expect(columnNames).toContain('hash');
@ -87,7 +87,7 @@ describe('Database Migration Integration Tests', () => {
WHERE TABLE_SCHEMA = 'mempool_test' WHERE TABLE_SCHEMA = 'mempool_test'
AND TABLE_NAME = 'pools'` AND TABLE_NAME = 'pools'`
); );
const columnNames = columns.map((col: any) => col.COLUMN_NAME); const columnNames = columns.map((col: any) => col.COLUMN_NAME);
expect(columnNames).toContain('id'); expect(columnNames).toContain('id');
expect(columnNames).toContain('name'); expect(columnNames).toContain('name');

View file

@ -43,7 +43,7 @@ describe('PoolsRepository Integration Tests', () => {
}); });
const pool = await PoolsRepository.$getPool('antpool'); const pool = await PoolsRepository.$getPool('antpool');
expect(pool).toBeDefined(); expect(pool).toBeDefined();
expect(pool!.name).toBe('AntPool'); expect(pool!.name).toBe('AntPool');
expect(pool!.slug).toBe('antpool'); expect(pool!.slug).toBe('antpool');
@ -64,7 +64,7 @@ describe('PoolsRepository Integration Tests', () => {
}); });
const pools = await PoolsRepository.$getPools(); const pools = await PoolsRepository.$getPools();
expect(pools.length).toBeGreaterThanOrEqual(3); expect(pools.length).toBeGreaterThanOrEqual(3);
}); });
@ -77,7 +77,7 @@ describe('PoolsRepository Integration Tests', () => {
}); });
const pool = await PoolsRepository.$getPool('multi-address-pool', false); const pool = await PoolsRepository.$getPool('multi-address-pool', false);
expect(pool).toBeDefined(); expect(pool).toBeDefined();
const poolAddresses = JSON.parse(pool!.addresses); const poolAddresses = JSON.parse(pool!.addresses);
expect(poolAddresses).toHaveLength(3); expect(poolAddresses).toHaveLength(3);
@ -93,7 +93,7 @@ describe('PoolsRepository Integration Tests', () => {
}); });
const pool = await PoolsRepository.$getPool('regex-pool', false); const pool = await PoolsRepository.$getPool('regex-pool', false);
expect(pool).toBeDefined(); expect(pool).toBeDefined();
const poolRegexes = JSON.parse(pool!.regexes); const poolRegexes = JSON.parse(pool!.regexes);
expect(poolRegexes).toHaveLength(2); expect(poolRegexes).toHaveLength(2);

View file

@ -45,7 +45,7 @@ export async function cleanupTestData(): Promise<void> {
try { try {
// Disable foreign key checks temporarily for faster cleanup // Disable foreign key checks temporarily for faster cleanup
await DB.query('SET FOREIGN_KEY_CHECKS = 0'); await DB.query('SET FOREIGN_KEY_CHECKS = 0');
for (const table of tables) { for (const table of tables) {
try { try {
// Use 'silent' error logging to avoid noise for optional tables that don't exist // Use 'silent' error logging to avoid noise for optional tables that don't exist
@ -55,7 +55,7 @@ export async function cleanupTestData(): Promise<void> {
// Silently ignore - no need to log since these are expected for optional features // Silently ignore - no need to log since these are expected for optional features
} }
} }
// Re-enable foreign key checks // Re-enable foreign key checks
await DB.query('SET FOREIGN_KEY_CHECKS = 1'); await DB.query('SET FOREIGN_KEY_CHECKS = 1');
} catch (error) { } catch (error) {
@ -143,7 +143,7 @@ export async function insertTestBlock(blockData: {
const size = blockData.size || 1000000; const size = blockData.size || 1000000;
const weight = blockData.weight || 4000000; const weight = blockData.weight || 4000000;
const txCount = blockData.tx_count || 2000; const txCount = blockData.tx_count || 2000;
await DB.query( await DB.query(
`INSERT INTO blocks ( `INSERT INTO blocks (
height, hash, blockTimestamp, size, weight, tx_count, height, hash, blockTimestamp, size, weight, tx_count,

View file

@ -30,7 +30,7 @@ describe('Common', () => {
expect(Common.isNonStandard(tx)).toEqual(true); expect(Common.isNonStandard(tx)).toEqual(true);
}); });
}); });
test('should not misclassify as nonstandard transactions', () => { test('should not misclassify as nonstandard transactions', () => {
randomTransactions.forEach((tx) => { randomTransactions.forEach((tx) => {
expect(Common.isNonStandard(tx)).toEqual(false); expect(Common.isNonStandard(tx)).toEqual(false);

View file

@ -144,7 +144,7 @@ describe('Mempool Backend Config', () => {
}); });
expect(config.MEMPOOL_SERVICES).toStrictEqual({ expect(config.MEMPOOL_SERVICES).toStrictEqual({
API: "", API: '',
ACCELERATIONS: false, ACCELERATIONS: false,
}); });

View file

@ -1,7 +1,7 @@
import { Application } from "express"; import { Application } from 'express';
import config from "../config"; import config from '../config';
import axios from "axios"; import axios from 'axios';
import logger from "../logger"; import logger from '../logger';
class AboutRoutes { class AboutRoutes {
public initRoutes(app: Application) { public initRoutes(app: Application) {

View file

@ -165,44 +165,44 @@ export namespace IBitcoinApi {
timeout: number; // (numeric) the median time past of a block at which the deployment is considered failed if not yet locked in timeout: number; // (numeric) the median time past of a block at which the deployment is considered failed if not yet locked in
since: number; // (numeric) height of the first block to which the status applies since: number; // (numeric) height of the first block to which the status applies
statistics: { // (object) numeric statistics about BIP9 signalling for a softfork (only for started status) statistics: { // (object) numeric statistics about BIP9 signalling for a softfork (only for started status)
period: number; // (numeric) the length in blocks of the BIP9 signalling period period: number; // (numeric) the length in blocks of the BIP9 signalling period
threshold: number; // (numeric) the number of blocks with the version bit set required to activate the feature threshold: number; // (numeric) the number of blocks with the version bit set required to activate the feature
elapsed: number; // (numeric) the number of blocks elapsed since the beginning of the current period elapsed: number; // (numeric) the number of blocks elapsed since the beginning of the current period
count: number; // (numeric) the number of blocks with the version bit set in the current period count: number; // (numeric) the number of blocks with the version bit set in the current period
possible: boolean; // (boolean) returns false if there are not enough blocks left in this period to pass activation threshold possible: boolean; // (boolean) returns false if there are not enough blocks left in this period to pass activation threshold
} }
} }
export interface BlockStats { export interface BlockStats {
"avgfee": number; 'avgfee': number;
"avgfeerate": number; 'avgfeerate': number;
"avgtxsize": number; 'avgtxsize': number;
"blockhash": string; 'blockhash': string;
"feerate_percentiles": [number, number, number, number, number]; 'feerate_percentiles': [number, number, number, number, number];
"height": number; 'height': number;
"ins": number; 'ins': number;
"maxfee": number; 'maxfee': number;
"maxfeerate": number; 'maxfeerate': number;
"maxtxsize": number; 'maxtxsize': number;
"medianfee": number; 'medianfee': number;
"mediantime": number; 'mediantime': number;
"mediantxsize": number; 'mediantxsize': number;
"minfee": number; 'minfee': number;
"minfeerate": number; 'minfeerate': number;
"mintxsize": number; 'mintxsize': number;
"outs": number; 'outs': number;
"subsidy": number; 'subsidy': number;
"swtotal_size": number; 'swtotal_size': number;
"swtotal_weight": number; 'swtotal_weight': number;
"swtxs": number; 'swtxs': number;
"time": number; 'time': number;
"total_out": number; 'total_out': number;
"total_size": number; 'total_size': number;
"total_weight": number; 'total_weight': number;
"totalfee": number; 'totalfee': number;
"txs": number; 'txs': number;
"utxo_increase": number; 'utxo_increase': number;
"utxo_size_inc": number; 'utxo_size_inc': number;
} }
} }
@ -213,26 +213,26 @@ export interface TestMempoolAcceptResult {
vsize?: number, vsize?: number,
fees?: { fees?: {
base: number, base: number,
"effective-feerate": number, 'effective-feerate': number,
"effective-includes": string[], 'effective-includes': string[],
}, },
['reject-reason']?: string, ['reject-reason']?: string,
} }
export interface SubmitPackageResult { export interface SubmitPackageResult {
package_msg: string; package_msg: string;
"tx-results": { [wtxid: string]: TxResult }; 'tx-results': { [wtxid: string]: TxResult };
"replaced-transactions"?: string[]; 'replaced-transactions'?: string[];
} }
export interface TxResult { export interface TxResult {
txid: string; txid: string;
"other-wtxid"?: string; 'other-wtxid'?: string;
vsize?: number; vsize?: number;
fees?: { fees?: {
base: number; base: number;
"effective-feerate"?: number; 'effective-feerate'?: number;
"effective-includes"?: string[]; 'effective-includes'?: string[];
}; };
error?: string; error?: string;
} }

View file

@ -130,7 +130,7 @@ class BitcoinApi implements AbstractBitcoinApi {
$getRawBlock(hash: string): Promise<Buffer> { $getRawBlock(hash: string): Promise<Buffer> {
return this.bitcoindClient.getBlock(hash, 0) return this.bitcoindClient.getBlock(hash, 0)
.then((raw: string) => Buffer.from(raw, "hex")); .then((raw: string) => Buffer.from(raw, 'hex'));
} }
$getBlockHash(height: number): Promise<string> { $getBlockHash(height: number): Promise<string> {

View file

@ -5,7 +5,7 @@ import { IEsploraApi } from './esplora-api.interface';
import { IElectrumApi } from './electrum-api.interface'; import { IElectrumApi } from './electrum-api.interface';
import BitcoinApi from './bitcoin-api'; import BitcoinApi from './bitcoin-api';
import logger from '../../logger'; import logger from '../../logger';
import crypto from "crypto-js"; import crypto from 'crypto-js';
import loadingIndicators from '../loading-indicators'; import loadingIndicators from '../loading-indicators';
import memoryCache from '../memory-cache'; import memoryCache from '../memory-cache';
@ -209,7 +209,7 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
async $getScriptHashUtxos(scripthash: string): Promise<IEsploraApi.UTXO[]> { async $getScriptHashUtxos(scripthash: string): Promise<IEsploraApi.UTXO[]> {
const utxos = await this.$getScriptHashUnspent(scripthash); const utxos = await this.$getScriptHashUnspent(scripthash);
const result: IEsploraApi.UTXO[] = []; const result: IEsploraApi.UTXO[] = [];
for(let utxo of utxos) { for(const utxo of utxos) {
if(utxo.height===0) { if(utxo.height===0) {
//Unconfirmed //Unconfirmed
result.push({ result.push({

View file

@ -106,7 +106,7 @@ class Blocks {
const mempool = memPool.getMempool(); const mempool = memPool.getMempool();
let foundInMempool = 0; let foundInMempool = 0;
let totalFound = 0; let totalFound = 0;
let missing = 0; const missing = 0;
// Copy existing transactions from the mempool // Copy existing transactions from the mempool
if (!onlyCoinbase) { if (!onlyCoinbase) {
@ -1365,15 +1365,15 @@ class Blocks {
/** /**
* Get 15 blocks * Get 15 blocks
* *
* Internally this function uses two methods to get the blocks, and * Internally this function uses two methods to get the blocks, and
* the method is automatically selected: * the method is automatically selected:
* - Using previous block hash links * - Using previous block hash links
* - Using block height * - Using block height
* *
* @param fromHeight * @param fromHeight
* @param limit * @param limit
* @returns * @returns
*/ */
public async $getBlocks(fromHeight?: number, limit: number = 15): Promise<BlockExtended[]> { public async $getBlocks(fromHeight?: number, limit: number = 15): Promise<BlockExtended[]> {
let currentHeight = fromHeight !== undefined ? fromHeight : this.currentBlockHeight; let currentHeight = fromHeight !== undefined ? fromHeight : this.currentBlockHeight;
@ -1405,9 +1405,9 @@ class Blocks {
/** /**
* Used for bulk block data query * Used for bulk block data query
* *
* @param fromHeight * @param fromHeight
* @param toHeight * @param toHeight
*/ */
public async $getBlocksBetweenHeight(fromHeight: number, toHeight: number): Promise<any> { public async $getBlocksBetweenHeight(fromHeight: number, toHeight: number): Promise<any> {
if (!Common.indexingEnabled()) { if (!Common.indexingEnabled()) {

View file

@ -911,7 +911,7 @@ export class Common {
if (id.indexOf('/') !== -1) { if (id.indexOf('/') !== -1) {
id = id.slice(0, -2); id = id.slice(0, -2);
} }
if (id.indexOf('x') !== -1) { // Already a short id if (id.indexOf('x') !== -1) { // Already a short id
return id; return id;
} }
@ -1081,7 +1081,7 @@ export class Common {
} }
static getTransactionFromRequest(req: Request, form: boolean): string { static getTransactionFromRequest(req: Request, form: boolean): string {
let rawTx: any = typeof req.body === 'object' && form const rawTx: any = typeof req.body === 'object' && form
? Object.values(req.body)[0] as any ? Object.values(req.body)[0] as any
: req.body; : req.body;
if (typeof rawTx !== 'string') { if (typeof rawTx !== 'string') {
@ -1182,7 +1182,7 @@ export class Common {
} }
} }
} }
}) });
} }
// Pass through the input string untouched // Pass through the input string untouched
@ -1220,14 +1220,14 @@ export class Common {
/** /**
* Class to calculate average fee rates of a list of transactions * Class to calculate average fee rates of a list of transactions
* at certain weight percentiles, in a single pass * at certain weight percentiles, in a single pass
* *
* init with: * init with:
* maxWeight - the total weight to measure percentiles relative to (e.g. 4MW for a single block) * maxWeight - the total weight to measure percentiles relative to (e.g. 4MW for a single block)
* percentileBandWidth - how many weight units to average over for each percentile (as a % of maxWeight) * percentileBandWidth - how many weight units to average over for each percentile (as a % of maxWeight)
* percentiles - an array of weight percentiles to compute, in % * percentiles - an array of weight percentiles to compute, in %
* *
* then call .processNext(tx) for each transaction, in descending order * then call .processNext(tx) for each transaction, in descending order
* *
* retrieve the final results with .getFeeStats() * retrieve the final results with .getFeeStats()
*/ */
export class OnlineFeeStatsCalculator { export class OnlineFeeStatsCalculator {

View file

@ -236,7 +236,7 @@ export function calculateMempoolTxCpfp(tx: MempoolTransactionExtended, mempool:
/** /**
* Given a root transaction and a list of in-mempool ancestors, * Given a root transaction and a list of in-mempool ancestors,
* Calculate the CPFP cluster * Calculate the CPFP cluster
* *
* @param tx * @param tx
* @param ancestors * @param ancestors
*/ */

View file

@ -566,8 +566,8 @@ class DatabaseMigration {
await this.$executeQuery('ALTER TABLE `blocks_templates` ADD INDEX `version` (`version`)'); await this.$executeQuery('ALTER TABLE `blocks_templates` ADD INDEX `version` (`version`)');
await this.updateToSchemaVersion(67); await this.updateToSchemaVersion(67);
} }
if (databaseSchemaVersion < 68 && config.MEMPOOL.NETWORK === "liquid") { if (databaseSchemaVersion < 68 && config.MEMPOOL.NETWORK === 'liquid') {
await this.$executeQuery('TRUNCATE TABLE elements_pegs'); await this.$executeQuery('TRUNCATE TABLE elements_pegs');
await this.$executeQuery('ALTER TABLE elements_pegs ADD PRIMARY KEY (txid, txindex);'); await this.$executeQuery('ALTER TABLE elements_pegs ADD PRIMARY KEY (txid, txindex);');
await this.$executeQuery(`UPDATE state SET number = 0 WHERE name = 'last_elements_block';`); await this.$executeQuery(`UPDATE state SET number = 0 WHERE name = 'last_elements_block';`);
@ -931,24 +931,24 @@ class DatabaseMigration {
// Version 34 // Version 34
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD clearnet_tor_nodes int(11) NOT NULL DEFAULT "0"'); await this.$executeQuery('ALTER TABLE `lightning_stats` ADD clearnet_tor_nodes int(11) NOT NULL DEFAULT "0"');
// Version 35 // Version 35
await this.$executeQuery('DELETE from `lightning_stats` WHERE added > "2021-09-19"'); await this.$executeQuery('DELETE from `lightning_stats` WHERE added > "2021-09-19"');
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD CONSTRAINT added_unique UNIQUE (added);'); await this.$executeQuery('ALTER TABLE `lightning_stats` ADD CONSTRAINT added_unique UNIQUE (added);');
// Version 36 // Version 36
await this.$executeQuery('ALTER TABLE `nodes` ADD status TINYINT NOT NULL DEFAULT "1"'); await this.$executeQuery('ALTER TABLE `nodes` ADD status TINYINT NOT NULL DEFAULT "1"');
// Version 37 // Version 37
await this.$executeQuery(this.getCreateLNNodesSocketsTableQuery(), await this.$checkIfTableExists('nodes_sockets')); await this.$executeQuery(this.getCreateLNNodesSocketsTableQuery(), await this.$checkIfTableExists('nodes_sockets'));
// Version 38 // Version 38
await this.$executeQuery(`TRUNCATE lightning_stats`); await this.$executeQuery(`TRUNCATE lightning_stats`);
await this.$executeQuery(`TRUNCATE node_stats`); await this.$executeQuery(`TRUNCATE node_stats`);
await this.$executeQuery('ALTER TABLE `lightning_stats` CHANGE `added` `added` timestamp NULL'); await this.$executeQuery('ALTER TABLE `lightning_stats` CHANGE `added` `added` timestamp NULL');
await this.$executeQuery('ALTER TABLE `node_stats` CHANGE `added` `added` timestamp NULL'); await this.$executeQuery('ALTER TABLE `node_stats` CHANGE `added` `added` timestamp NULL');
await this.updateToSchemaVersion(38); await this.updateToSchemaVersion(38);
// Version 39 // Version 39
await this.$executeQuery('ALTER TABLE `nodes` ADD alias_search TEXT NULL DEFAULT NULL AFTER `alias`'); await this.$executeQuery('ALTER TABLE `nodes` ADD alias_search TEXT NULL DEFAULT NULL AFTER `alias`');
await this.$executeQuery('ALTER TABLE nodes ADD FULLTEXT(alias_search)'); await this.$executeQuery('ALTER TABLE nodes ADD FULLTEXT(alias_search)');
@ -963,7 +963,7 @@ class DatabaseMigration {
// Version 42 // Version 42
await this.$executeQuery('ALTER TABLE `channels` ADD closing_resolved tinyint(1) DEFAULT 0'); await this.$executeQuery('ALTER TABLE `channels` ADD closing_resolved tinyint(1) DEFAULT 0');
// Version 43 // Version 43
await this.$executeQuery(this.getCreateLNNodeRecordsTableQuery(), await this.$checkIfTableExists('nodes_records')); await this.$executeQuery(this.getCreateLNNodeRecordsTableQuery(), await this.$checkIfTableExists('nodes_records'));
@ -972,7 +972,7 @@ class DatabaseMigration {
// Version 45 // Version 45
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD fresh_txs JSON DEFAULT "[]"'); await this.$executeQuery('ALTER TABLE `blocks_audits` ADD fresh_txs JSON DEFAULT "[]"');
// Version 48 // Version 48
await this.$executeQuery('ALTER TABLE `channels` ADD source_checked tinyint(1) DEFAULT 0'); await this.$executeQuery('ALTER TABLE `channels` ADD source_checked tinyint(1) DEFAULT 0');
await this.$executeQuery('ALTER TABLE `channels` ADD closing_fee bigint(20) unsigned DEFAULT 0'); await this.$executeQuery('ALTER TABLE `channels` ADD closing_fee bigint(20) unsigned DEFAULT 0');
@ -1002,13 +1002,13 @@ class DatabaseMigration {
// Version 62 // Version 62
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD expected_fees BIGINT UNSIGNED DEFAULT NULL'); await this.$executeQuery('ALTER TABLE `blocks_audits` ADD expected_fees BIGINT UNSIGNED DEFAULT NULL');
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD expected_weight BIGINT UNSIGNED DEFAULT NULL'); await this.$executeQuery('ALTER TABLE `blocks_audits` ADD expected_weight BIGINT UNSIGNED DEFAULT NULL');
// Version 63 // Version 63
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD fullrbf_txs JSON DEFAULT "[]"'); await this.$executeQuery('ALTER TABLE `blocks_audits` ADD fullrbf_txs JSON DEFAULT "[]"');
// Version 64 // Version 64
await this.$executeQuery('ALTER TABLE `nodes` ADD features text NULL'); await this.$executeQuery('ALTER TABLE `nodes` ADD features text NULL');
// Version 65 // Version 65
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD accelerated_txs JSON DEFAULT "[]"'); await this.$executeQuery('ALTER TABLE `blocks_audits` ADD accelerated_txs JSON DEFAULT "[]"');
@ -1044,8 +1044,8 @@ class DatabaseMigration {
ADD INDEX \`closing_reason\` (\`closing_reason\`), ADD INDEX \`closing_reason\` (\`closing_reason\`),
ADD INDEX \`closing_resolved\` (\`closing_resolved\`) ADD INDEX \`closing_resolved\` (\`closing_resolved\`)
`); `);
// Version 86 // Version 86
await this.$executeQuery(` await this.$executeQuery(`
ALTER TABLE \`nodes\` ALTER TABLE \`nodes\`
ADD INDEX \`status\` (\`status\`), ADD INDEX \`status\` (\`status\`),
@ -1058,20 +1058,20 @@ class DatabaseMigration {
// Version 87 // Version 87
await this.$executeQuery('ALTER TABLE `nodes_sockets` ADD INDEX `type` (`type`)'); await this.$executeQuery('ALTER TABLE `nodes_sockets` ADD INDEX `type` (`type`)');
await this.updateToSchemaVersion(87); await this.updateToSchemaVersion(87);
// Version 88 // Version 88
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD INDEX `added` (`added`)'); await this.$executeQuery('ALTER TABLE `lightning_stats` ADD INDEX `added` (`added`)');
// Version 89 // Version 89
await this.$executeQuery('ALTER TABLE `geo_names` ADD INDEX `names` (`names`)'); await this.$executeQuery('ALTER TABLE `geo_names` ADD INDEX `names` (`names`)');
// Version 90 // Version 90
await this.$executeQuery('ALTER TABLE `hashrates` ADD INDEX `type` (`type`)'); await this.$executeQuery('ALTER TABLE `hashrates` ADD INDEX `type` (`type`)');
// Version 91 // Version 91
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD INDEX `time` (`time`)'); await this.$executeQuery('ALTER TABLE `blocks_audits` ADD INDEX `time` (`time`)');
} }
if (config.MEMPOOL.NETWORK !== 'liquid') { if (config.MEMPOOL.NETWORK !== 'liquid') {
// Apply all the liquid specific migrations to all other networks // Apply all the liquid specific migrations to all other networks
// Version 68 // Version 68
@ -1093,7 +1093,7 @@ class DatabaseMigration {
ADD INDEX \`bitcoinaddress\` (\`bitcoinaddress\`), ADD INDEX \`bitcoinaddress\` (\`bitcoinaddress\`),
ADD INDEX \`bitcointxid\` (\`bitcointxid\`) ADD INDEX \`bitcointxid\` (\`bitcointxid\`)
`); `);
// Version 93 // Version 93
await this.$executeQuery(` await this.$executeQuery(`
ALTER TABLE \`federation_txos\` ALTER TABLE \`federation_txos\`
@ -1456,7 +1456,7 @@ class DatabaseMigration {
pegtxid varchar(65) NOT NULL, pegtxid varchar(65) NOT NULL,
pegindex int(11) NOT NULL, pegindex int(11) NOT NULL,
pegblocktime int(11) unsigned NOT NULL, pegblocktime int(11) unsigned NOT NULL,
PRIMARY KEY (txid, txindex), PRIMARY KEY (txid, txindex),
FOREIGN KEY (bitcoinaddress) REFERENCES federation_addresses (bitcoinaddress) FOREIGN KEY (bitcoinaddress) REFERENCES federation_addresses (bitcoinaddress)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
} }

View file

@ -53,7 +53,7 @@ class ChannelsApi {
GROUP BY nodes_1.public_key, nodes_2.public_key GROUP BY nodes_1.public_key, nodes_2.public_key
ORDER BY channels.capacity DESC ORDER BY channels.capacity DESC
LIMIT 10000 LIMIT 10000
`; `;
} }
const [rows]: any = await DB.query(query, params); const [rows]: any = await DB.query(query, params);
@ -241,10 +241,10 @@ class ChannelsApi {
let [feeRates2]: any = await DB.query(query); let [feeRates2]: any = await DB.query(query);
feeRates2 = feeRates2.map(rate => rate.node2_fee_rate); feeRates2 = feeRates2.map(rate => rate.node2_fee_rate);
let feeRates = (feeRates1.concat(feeRates2)).sort((a, b) => a - b); const feeRates = (feeRates1.concat(feeRates2)).sort((a, b) => a - b);
let avgFeeRate = 0; let avgFeeRate = 0;
for (const rate of feeRates) { for (const rate of feeRates) {
avgFeeRate += rate; avgFeeRate += rate;
} }
avgFeeRate /= feeRates.length; avgFeeRate /= feeRates.length;
const medianFeeRate = feeRates[Math.floor(feeRates.length / 2)]; const medianFeeRate = feeRates[Math.floor(feeRates.length / 2)];
@ -257,14 +257,14 @@ class ChannelsApi {
let [baseFees2]: any = await DB.query(query); let [baseFees2]: any = await DB.query(query);
baseFees2 = baseFees2.map(rate => rate.node2_base_fee_mtokens); baseFees2 = baseFees2.map(rate => rate.node2_base_fee_mtokens);
let baseFees = (baseFees1.concat(baseFees2)).sort((a, b) => a - b); const baseFees = (baseFees1.concat(baseFees2)).sort((a, b) => a - b);
let avgBaseFee = 0; let avgBaseFee = 0;
for (const fee of baseFees) { for (const fee of baseFees) {
avgBaseFee += fee; avgBaseFee += fee;
} }
avgBaseFee /= baseFees.length; avgBaseFee /= baseFees.length;
const medianBaseFee = feeRates[Math.floor(baseFees.length / 2)]; const medianBaseFee = feeRates[Math.floor(baseFees.length / 2)];
return { return {
avgCapacity: parseInt(avgCapacity[0].avgCapacity, 10), avgCapacity: parseInt(avgCapacity[0].avgCapacity, 10),
avgFeeRate: avgFeeRate, avgFeeRate: avgFeeRate,
@ -272,7 +272,7 @@ class ChannelsApi {
medianCapacity: medianCapacity, medianCapacity: medianCapacity,
medianFeeRate: medianFeeRate, medianFeeRate: medianFeeRate,
medianBaseFee: medianBaseFee, medianBaseFee: medianBaseFee,
} };
} catch (e) { } catch (e) {
logger.err(`Cannot calculate channels statistics. Reason: ${e instanceof Error ? e.message : e}`); logger.err(`Cannot calculate channels statistics. Reason: ${e instanceof Error ? e.message : e}`);
@ -456,7 +456,7 @@ class ChannelsApi {
allChannels = allChannels.slice(0, 1000); allChannels = allChannels.slice(0, 1000);
} }
const channels: any[] = [] const channels: any[] = [];
for (const row of allChannels) { for (const row of allChannels) {
let channel; let channel;
if (index >= 0) { if (index >= 0) {

View file

@ -34,7 +34,7 @@ class NodesApi {
`; `;
const [maximums]: any[] = await DB.query(query); const [maximums]: any[] = await DB.query(query);
return { return {
maxLiquidity: maximums[0].maxLiquidity, maxLiquidity: maximums[0].maxLiquidity,
maxChannels: maximums[0].maxChannels, maxChannels: maximums[0].maxChannels,
@ -78,7 +78,7 @@ class NodesApi {
node.city = JSON.parse(node.city); node.city = JSON.parse(node.city);
node.country = JSON.parse(node.country); node.country = JSON.parse(node.country);
// Features // Features
node.features = JSON.parse(node.features); node.features = JSON.parse(node.features);
node.featuresBits = null; node.featuresBits = null;
if (node.features) { if (node.features) {
@ -87,7 +87,7 @@ class NodesApi {
maxBit = Math.max(maxBit, feature.bit); maxBit = Math.max(maxBit, feature.bit);
} }
maxBit = Math.ceil(maxBit / 4) * 4 - 1; maxBit = Math.ceil(maxBit / 4) * 4 - 1;
node.featuresBits = new Array(maxBit + 1).fill(0); node.featuresBits = new Array(maxBit + 1).fill(0);
for (const feature of node.features) { for (const feature of node.features) {
node.featuresBits[feature.bit] = 1; node.featuresBits[feature.bit] = 1;
@ -394,7 +394,7 @@ class NodesApi {
try { try {
const publicKeySearch = search.replace(/[^a-zA-Z0-9]/g, '') + '%'; const publicKeySearch = search.replace(/[^a-zA-Z0-9]/g, '') + '%';
const aliasSearch = search const aliasSearch = search
.replace(/[-_.]/g, ' ') // Replace all -_. characters with empty space. Eg: "ln.nicehash" becomes "ln nicehash". .replace(/[-_.]/g, ' ') // Replace all -_. characters with empty space. Eg: "ln.nicehash" becomes "ln nicehash".
.replace(/[^a-zA-Z0-9 ]/g, '') // Remove all special characters and keep just A to Z, 0 to 9. .replace(/[^a-zA-Z0-9 ]/g, '') // Remove all special characters and keep just A to Z, 0 to 9.
.split(' ') .split(' ')
.filter(key => key.length) .filter(key => key.length)
@ -455,7 +455,7 @@ class NodesApi {
} else if (ispList[isp2].ids.includes(channel.isp2ID) === false) { } else if (ispList[isp2].ids.includes(channel.isp2ID) === false) {
ispList[isp2].ids.push(channel.isp2ID); ispList[isp2].ids.push(channel.isp2ID);
} }
ispList[isp1].capacity += channel.capacity; ispList[isp1].capacity += channel.capacity;
ispList[isp1].channels += 1; ispList[isp1].channels += 1;
ispList[isp1].nodes[channel.node1PublicKey] = true; ispList[isp1].nodes[channel.node1PublicKey] = true;
@ -463,7 +463,7 @@ class NodesApi {
ispList[isp2].channels += 1; ispList[isp2].channels += 1;
ispList[isp2].nodes[channel.node2PublicKey] = true; ispList[isp2].nodes[channel.node2PublicKey] = true;
} }
const ispRanking: any[] = []; const ispRanking: any[] = [];
for (const isp of Object.keys(ispList)) { for (const isp of Object.keys(ispList)) {
ispRanking.push([ ispRanking.push([
@ -494,7 +494,7 @@ class NodesApi {
`; `;
const [clearnetCapacity]: any = await DB.query(query); const [clearnetCapacity]: any = await DB.query(query);
// Get the total capacity of all channels which have both nodes on Tor // Get the total capacity of all channels which have both nodes on Tor
query = ` query = `
SELECT SUM(capacity) as capacity SELECT SUM(capacity) as capacity
FROM ( FROM (
@ -642,11 +642,11 @@ class NodesApi {
for (const country of nodesCountPerCountry) { for (const country of nodesCountPerCountry) {
nodesPerCountry.push({ nodesPerCountry.push({
name: JSON.parse(country.names), name: JSON.parse(country.names),
iso: country.iso_code, iso: country.iso_code,
count: country.nodesCount, count: country.nodesCount,
share: Math.floor(country.nodesCount / nodesWithAS[0].total * 10000) / 100, share: Math.floor(country.nodesCount / nodesWithAS[0].total * 10000) / 100,
capacity: country.capacity, capacity: country.capacity,
}) });
} }
return nodesPerCountry; return nodesPerCountry;
@ -665,7 +665,7 @@ class NodesApi {
if ((node.last_update ?? 0) < 1514736061) { // January 1st 2018 if ((node.last_update ?? 0) < 1514736061) { // January 1st 2018
node.last_update = null; node.last_update = null;
} }
const uniqueAddr = [...new Set(node.addresses?.map(a => a.addr))]; const uniqueAddr = [...new Set(node.addresses?.map(a => a.addr))];
const formattedSockets = (uniqueAddr.join(',')) ?? ''; const formattedSockets = (uniqueAddr.join(',')) ?? '';

View file

@ -39,7 +39,7 @@ class NodesRoutes {
private async $getNodeGroup(req: Request, res: Response) { private async $getNodeGroup(req: Request, res: Response) {
try { try {
let nodesList; let nodesList;
let nodes: any[] = []; const nodes: any[] = [];
switch (config.MEMPOOL.NETWORK) { switch (config.MEMPOOL.NETWORK) {
case 'testnet': case 'testnet':
nodesList = [ nodesList = [
@ -174,7 +174,7 @@ class NodesRoutes {
]; ];
} }
for (let pubKey of nodesList) { for (const pubKey of nodesList) {
try { try {
const node = await nodesApi.$getNode(pubKey); const node = await nodesApi.$getNode(pubKey);
if (node) { if (node) {

View file

@ -1,5 +1,5 @@
import fs from 'fs'; import fs from 'fs';
import path from "path"; import path from 'path';
const { spawnSync } = require('child_process'); const { spawnSync } = require('child_process');
function getVersion(): string { function getVersion(): string {
@ -29,9 +29,9 @@ function getGitCommit(): string {
const versionInfo = { const versionInfo = {
version: getVersion(), version: getVersion(),
gitCommit: getGitCommit() gitCommit: getGitCommit()
} };
fs.writeFileSync( fs.writeFileSync(
path.join(__dirname, 'version.json'), path.join(__dirname, 'version.json'),
JSON.stringify(versionInfo, null, 2) + "\n" JSON.stringify(versionInfo, null, 2) + '\n'
); );

View file

@ -116,7 +116,7 @@ class LightningError extends Error {
const defaultRpcPath = path.join(homedir(), '.lightning') const defaultRpcPath = path.join(homedir(), '.lightning')
, fStat = (...p) => statSync(path.join(...p)) , fStat = (...p) => statSync(path.join(...p))
, fExists = (...p) => existsSync(path.join(...p)) , fExists = (...p) => existsSync(path.join(...p));
export default class CLightningClient extends EventEmitter implements AbstractLightningApi { export default class CLightningClient extends EventEmitter implements AbstractLightningApi {
private rpcPath: string; private rpcPath: string;
@ -141,9 +141,9 @@ export default class CLightningClient extends EventEmitter implements AbstractLi
// main data directory provided, default to using the bitcoin mainnet subdirectory // main data directory provided, default to using the bitcoin mainnet subdirectory
// to be removed in v0.2.0 // to be removed in v0.2.0
else if (fExists(rpcPath, 'bitcoin', 'lightning-rpc')) { else if (fExists(rpcPath, 'bitcoin', 'lightning-rpc')) {
logger.warn(`${rpcPath}/lightning-rpc is missing, using the bitcoin mainnet subdirectory at ${rpcPath}/bitcoin instead.`, logger.tags.ln) logger.warn(`${rpcPath}/lightning-rpc is missing, using the bitcoin mainnet subdirectory at ${rpcPath}/bitcoin instead.`, logger.tags.ln);
logger.warn(`specifying the main lightning data directory is deprecated, please specify the network directory explicitly.\n`, logger.tags.ln) logger.warn(`specifying the main lightning data directory is deprecated, please specify the network directory explicitly.\n`, logger.tags.ln);
rpcPath = path.join(rpcPath, 'bitcoin', 'lightning-rpc') rpcPath = path.join(rpcPath, 'bitcoin', 'lightning-rpc');
} }
} }

View file

@ -46,10 +46,10 @@ class LndApi implements AbstractLightningApi {
for (const node of graph.nodes) { for (const node of graph.nodes) {
const nodeFeatures: ILightningApi.Feature[] = []; const nodeFeatures: ILightningApi.Feature[] = [];
for (const bit in node.features) { for (const bit in node.features) {
nodeFeatures.push({ nodeFeatures.push({
bit: parseInt(bit, 10), bit: parseInt(bit, 10),
name: node.features[bit].name, name: node.features[bit].name,
is_required: node.features[bit].is_required, is_required: node.features[bit].is_required,
is_known: node.features[bit].is_known, is_known: node.features[bit].is_known,
}); });

View file

@ -87,7 +87,7 @@ class ElementsParser {
logger.debug(`Saved L-BTC peg from Liquid block height #${height} with TXID ${txid}.`); logger.debug(`Saved L-BTC peg from Liquid block height #${height} with TXID ${txid}.`);
if (amount > 0) { // Peg-in if (amount > 0) { // Peg-in
// Add the address to the federation addresses table // Add the address to the federation addresses table
await DB.query(`INSERT IGNORE INTO federation_addresses (bitcoinaddress) VALUES (?)`, [bitcoinaddress]); await DB.query(`INSERT IGNORE INTO federation_addresses (bitcoinaddress) VALUES (?)`, [bitcoinaddress]);
@ -95,7 +95,7 @@ class ElementsParser {
const query_utxos = `INSERT IGNORE INTO federation_txos (txid, txindex, bitcoinaddress, amount, blocknumber, blocktime, unspent, lastblockupdate, lasttimeupdate, timelock, expiredAt, emergencyKey, pegtxid, pegindex, pegblocktime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; const query_utxos = `INSERT IGNORE INTO federation_txos (txid, txindex, bitcoinaddress, amount, blocknumber, blocktime, unspent, lastblockupdate, lasttimeupdate, timelock, expiredAt, emergencyKey, pegtxid, pegindex, pegblocktime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
const params_utxos: (string | number)[] = [bitcointxid, bitcoinindex, bitcoinaddress, amount, bitcoinblock, bitcoinBlockTime, 1, bitcoinblock - 1, 0, 4032, 0, 0, txid, txindex, blockTime]; const params_utxos: (string | number)[] = [bitcointxid, bitcoinindex, bitcoinaddress, amount, bitcoinblock, bitcoinBlockTime, 1, bitcoinblock - 1, 0, 4032, 0, 0, txid, txindex, blockTime];
await DB.query(query_utxos, params_utxos); await DB.query(query_utxos, params_utxos);
const [minBlockUpdate] = await DB.query(`SELECT MIN(lastblockupdate) AS lastblockupdate FROM federation_txos WHERE unspent = 1`) const [minBlockUpdate] = await DB.query(`SELECT MIN(lastblockupdate) AS lastblockupdate FROM federation_txos WHERE unspent = 1`);
await this.$saveLastBlockAuditToDatabase(minBlockUpdate[0]['lastblockupdate']); await this.$saveLastBlockAuditToDatabase(minBlockUpdate[0]['lastblockupdate']);
logger.debug(`Saved new Federation UTXO ${bitcointxid}:${bitcoinindex} belonging to ${bitcoinaddress} to federation txos`); logger.debug(`Saved new Federation UTXO ${bitcointxid}:${bitcoinindex} belonging to ${bitcoinaddress} to federation txos`);
@ -174,7 +174,7 @@ class ElementsParser {
const runningFor = (Date.now() / 1000) - startedAt; const runningFor = (Date.now() / 1000) - startedAt;
const blockPerSeconds = indexedThisRun / elapsedSeconds; const blockPerSeconds = indexedThisRun / elapsedSeconds;
indexingSpeeds.push(blockPerSeconds); indexingSpeeds.push(blockPerSeconds);
if (indexingSpeeds.length > 100) indexingSpeeds.shift(); // Keep the length of the up to 100 last indexing speeds if (indexingSpeeds.length > 100) {indexingSpeeds.shift();} // Keep the length of the up to 100 last indexing speeds
const meanIndexingSpeed = indexingSpeeds.reduce((a, b) => a + b, 0) / indexingSpeeds.length; const meanIndexingSpeed = indexingSpeeds.reduce((a, b) => a + b, 0) / indexingSpeeds.length;
const eta = (auditProgress.confirmedTip - auditProgress.lastBlockAudit) / meanIndexingSpeed; const eta = (auditProgress.confirmedTip - auditProgress.lastBlockAudit) / meanIndexingSpeed;
logger.debug(`Scanning ${utxos.length} Federation UTXOs and ${redeemAddresses.length} Peg-Out Addresses at Bitcoin block height #${auditProgress.lastBlockAudit} / #${auditProgress.confirmedTip} | ~${meanIndexingSpeed.toFixed(2)} blocks/sec | elapsed: ${(runningFor / 60).toFixed(0)} minutes | ETA: ${(eta / 60).toFixed(0)} minutes`); logger.debug(`Scanning ${utxos.length} Federation UTXOs and ${redeemAddresses.length} Peg-Out Addresses at Bitcoin block height #${auditProgress.lastBlockAudit} / #${auditProgress.confirmedTip} | ~${meanIndexingSpeed.toFixed(2)} blocks/sec | elapsed: ${(runningFor / 60).toFixed(0)} minutes | ETA: ${(eta / 60).toFixed(0)} minutes`);
@ -189,7 +189,7 @@ class ElementsParser {
await this.$parseBitcoinBlock(block, spentAsTip, unspentAsTip, auditProgress.confirmedTip, redeemAddresses); await this.$parseBitcoinBlock(block, spentAsTip, unspentAsTip, auditProgress.confirmedTip, redeemAddresses);
// Finally, update the lastblockupdate of the remaining UTXOs and save to the database // Finally, update the lastblockupdate of the remaining UTXOs and save to the database
const [minBlockUpdate] = await DB.query(`SELECT MIN(lastblockupdate) AS lastblockupdate FROM federation_txos WHERE unspent = 1`) const [minBlockUpdate] = await DB.query(`SELECT MIN(lastblockupdate) AS lastblockupdate FROM federation_txos WHERE unspent = 1`);
await this.$saveLastBlockAuditToDatabase(minBlockUpdate[0]['lastblockupdate']); await this.$saveLastBlockAuditToDatabase(minBlockUpdate[0]['lastblockupdate']);
auditProgress = await this.$getAuditProgress(); auditProgress = await this.$getAuditProgress();
@ -201,11 +201,11 @@ class ElementsParser {
} catch (e) { } catch (e) {
this.isUtxosUpdatingRunning = false; this.isUtxosUpdatingRunning = false;
throw new Error(e instanceof Error ? e.message : 'Error'); throw new Error(e instanceof Error ? e.message : 'Error');
} }
} }
// Get the UTXOs that need to be scanned in block height (UTXOs that were last updated in the block height - 1) // Get the UTXOs that need to be scanned in block height (UTXOs that were last updated in the block height - 1)
protected async $getFederationUtxosToScan(height: number) { protected async $getFederationUtxosToScan(height: number) {
const query = `SELECT txid, txindex, bitcoinaddress, amount, blocknumber, timelock, expiredAt FROM federation_txos WHERE lastblockupdate = ? AND unspent = 1`; const query = `SELECT txid, txindex, bitcoinaddress, amount, blocknumber, timelock, expiredAt FROM federation_txos WHERE lastblockupdate = ? AND unspent = 1`;
const [rows] = await DB.query(query, [height - 1]); const [rows] = await DB.query(query, [height - 1]);
return rows as any[]; return rows as any[];
@ -220,7 +220,7 @@ class ElementsParser {
const result = await bitcoinSecondClient.getTxOut(utxo.txid, utxo.txindex, false); const result = await bitcoinSecondClient.getTxOut(utxo.txid, utxo.txindex, false);
result ? unspentAsTip.push(utxo) : spentAsTip.push(utxo); result ? unspentAsTip.push(utxo) : spentAsTip.push(utxo);
} }
return {spentAsTip, unspentAsTip}; return {spentAsTip, unspentAsTip};
} }
@ -296,7 +296,7 @@ class ElementsParser {
} }
} }
for (const utxo of spentAsTip) { for (const utxo of spentAsTip) {
if (utxo.expiredAt === 0 && block.height >= utxo.blocknumber + utxo.timelock) { // The UTXO is expiring in this block if (utxo.expiredAt === 0 && block.height >= utxo.blocknumber + utxo.timelock) { // The UTXO is expiring in this block
await DB.query(`UPDATE federation_txos SET lastblockupdate = ?, expiredAt = ? WHERE txid = ? AND txindex = ?`, [block.height, block.time, utxo.txid, utxo.txindex]); await DB.query(`UPDATE federation_txos SET lastblockupdate = ?, expiredAt = ? WHERE txid = ? AND txindex = ?`, [block.height, block.time, utxo.txid, utxo.txindex]);
} else { } else {
@ -308,7 +308,7 @@ class ElementsParser {
if (utxo.expiredAt === 0 && block.height >= utxo.blocknumber + utxo.timelock) { // The UTXO is expiring in this block if (utxo.expiredAt === 0 && block.height >= utxo.blocknumber + utxo.timelock) { // The UTXO is expiring in this block
await DB.query(`UPDATE federation_txos SET unspent = 0, lastblockupdate = ?, expiredAt = ? WHERE txid = ? AND txindex = ?`, [confirmedTip, block.time, utxo.txid, utxo.txindex]); await DB.query(`UPDATE federation_txos SET unspent = 0, lastblockupdate = ?, expiredAt = ? WHERE txid = ? AND txindex = ?`, [confirmedTip, block.time, utxo.txid, utxo.txindex]);
} else if (utxo.expiredAt === 0 && confirmedTip >= utxo.blocknumber + utxo.timelock) { // The UTXO is expiring before the tip: we need to keep track of it } else if (utxo.expiredAt === 0 && confirmedTip >= utxo.blocknumber + utxo.timelock) { // The UTXO is expiring before the tip: we need to keep track of it
await DB.query(`UPDATE federation_txos SET lastblockupdate = ? WHERE txid = ? AND txindex = ?`, [utxo.blocknumber + utxo.timelock - 1, utxo.txid, utxo.txindex]); await DB.query(`UPDATE federation_txos SET lastblockupdate = ? WHERE txid = ? AND txindex = ?`, [utxo.blocknumber + utxo.timelock - 1, utxo.txid, utxo.txindex]);
} else { } else {
await DB.query(`UPDATE federation_txos SET lastblockupdate = ? WHERE txid = ? AND txindex = ?`, [confirmedTip, utxo.txid, utxo.txindex]); await DB.query(`UPDATE federation_txos SET lastblockupdate = ? WHERE txid = ? AND txindex = ?`, [confirmedTip, utxo.txid, utxo.txindex]);
} }
@ -336,7 +336,7 @@ class ElementsParser {
return { return {
bitcoinBlocks: result.blocks, bitcoinBlocks: result.blocks,
bitcoinHeaders: result.headers, bitcoinHeaders: result.headers,
} };
} }
protected async $getLastBlockAudit(): Promise<number> { protected async $getLastBlockAudit(): Promise<number> {
@ -384,7 +384,7 @@ class ElementsParser {
AND AND
(expiredAt = 0 OR expiredAt > UNIX_TIMESTAMP(LAST_DAY(FROM_UNIXTIME(blocktime)) + INTERVAL 1 DAY)) (expiredAt = 0 OR expiredAt > UNIX_TIMESTAMP(LAST_DAY(FROM_UNIXTIME(blocktime)) + INTERVAL 1 DAY))
GROUP BY GROUP BY
date;`; date;`;
const [rows] = await DB.query(query); const [rows] = await DB.query(query);
return rows; return rows;
} }
@ -444,7 +444,7 @@ class ElementsParser {
const [rows] = await DB.query(query); const [rows] = await DB.query(query);
return rows; return rows;
} }
// Get the total number of federation addresses // Get the total number of federation addresses
public async $getFederationAddressesNumber(): Promise<any> { public async $getFederationAddressesNumber(): Promise<any> {
const query = `SELECT COUNT(DISTINCT bitcoinaddress) AS address_count FROM federation_txos WHERE unspent = 1 AND expiredAt = 0;`; const query = `SELECT COUNT(DISTINCT bitcoinaddress) AS address_count FROM federation_txos WHERE unspent = 1 AND expiredAt = 0;`;

View file

@ -14,7 +14,7 @@ class LiquidRoutes {
.get(config.MEMPOOL.API_URL_PREFIX + 'asset/:assetId/icon', this.getLiquidIcon) .get(config.MEMPOOL.API_URL_PREFIX + 'asset/:assetId/icon', this.getLiquidIcon)
.get(config.MEMPOOL.API_URL_PREFIX + 'assets/group/:id', this.$getAssetGroup) .get(config.MEMPOOL.API_URL_PREFIX + 'assets/group/:id', this.$getAssetGroup)
; ;
if (config.DATABASE.ENABLED) { if (config.DATABASE.ENABLED) {
app app
.get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs', this.$getElementsPegs) .get(config.MEMPOOL.API_URL_PREFIX + 'liquid/pegs', this.$getElementsPegs)

View file

@ -38,7 +38,7 @@ class Mempool {
private mempoolProtection = 0; private mempoolProtection = 0;
private latestTransactions: any[] = []; private latestTransactions: any[] = [];
private ESPLORA_MISSING_TX_WARNING_THRESHOLD = 100; private ESPLORA_MISSING_TX_WARNING_THRESHOLD = 100;
private SAMPLE_TIME = 10000; // In ms private SAMPLE_TIME = 10000; // In ms
private timer = new Date().getTime(); private timer = new Date().getTime();
private missingTxCount = 0; private missingTxCount = 0;
@ -51,15 +51,15 @@ class Mempool {
// Initialize mempoolInfo here to avoid circular dependency issues // Initialize mempoolInfo here to avoid circular dependency issues
// Use config directly instead of Common.isLiquid() to break circular dependency // Use config directly instead of Common.isLiquid() to break circular dependency
const isLiquid = config.MEMPOOL.NETWORK === 'liquid' || config.MEMPOOL.NETWORK === 'liquidtestnet'; const isLiquid = config.MEMPOOL.NETWORK === 'liquid' || config.MEMPOOL.NETWORK === 'liquidtestnet';
this.mempoolInfo = { this.mempoolInfo = {
loaded: false, loaded: false,
size: 0, size: 0,
bytes: 0, bytes: 0,
usage: 0, usage: 0,
total_fee: 0, total_fee: 0,
maxmempool: 300000000, maxmempool: 300000000,
mempoolminfee: isLiquid ? 0.00000100 : 0.00001000, mempoolminfee: isLiquid ? 0.00000100 : 0.00001000,
minrelaytxfee: isLiquid ? 0.00000100 : 0.00001000 minrelaytxfee: isLiquid ? 0.00000100 : 0.00001000
}; };
this.txPerSecondInterval = setInterval(this.updateTxPerSecond.bind(this), 1000); this.txPerSecondInterval = setInterval(this.updateTxPerSecond.bind(this), 1000);
} }

View file

@ -1,12 +1,12 @@
import { Application, Request, Response } from 'express'; import { Application, Request, Response } from 'express';
import config from "../../config"; import config from '../../config';
import logger from '../../logger'; import logger from '../../logger';
import BlocksAuditsRepository from '../../repositories/BlocksAuditsRepository'; import BlocksAuditsRepository from '../../repositories/BlocksAuditsRepository';
import BlocksRepository from '../../repositories/BlocksRepository'; import BlocksRepository from '../../repositories/BlocksRepository';
import DifficultyAdjustmentsRepository from '../../repositories/DifficultyAdjustmentsRepository'; import DifficultyAdjustmentsRepository from '../../repositories/DifficultyAdjustmentsRepository';
import HashratesRepository from '../../repositories/HashratesRepository'; import HashratesRepository from '../../repositories/HashratesRepository';
import bitcoinClient from '../bitcoin/bitcoin-client'; import bitcoinClient from '../bitcoin/bitcoin-client';
import mining from "./mining"; import mining from './mining';
import PricesRepository from '../../repositories/PricesRepository'; import PricesRepository from '../../repositories/PricesRepository';
import AccelerationRepository from '../../repositories/AccelerationRepository'; import AccelerationRepository from '../../repositories/AccelerationRepository';
import accelerationApi from '../services/acceleration'; import accelerationApi from '../services/acceleration';

View file

@ -26,7 +26,7 @@ class Mining {
private blocksPriceIndexingRunning = false; private blocksPriceIndexingRunning = false;
public lastHashrateIndexingDate: number | null = null; public lastHashrateIndexingDate: number | null = null;
public lastWeeklyHashrateIndexingDate: number | null = null; public lastWeeklyHashrateIndexingDate: number | null = null;
public reindexHashrateRequested = false; public reindexHashrateRequested = false;
public reindexDifficultyAdjustmentRequested = false; public reindexDifficultyAdjustmentRequested = false;
@ -66,7 +66,7 @@ class Mining {
{from, to} {from, to}
); );
} }
/** /**
* Get historical block rewards * Get historical block rewards
*/ */
@ -175,8 +175,8 @@ class Mining {
const blockCount1w: number = await BlocksRepository.$blockCount(pool.id, '1w'); const blockCount1w: number = await BlocksRepository.$blockCount(pool.id, '1w');
const totalBlock1w: number = await BlocksRepository.$blockCount(null, '1w'); const totalBlock1w: number = await BlocksRepository.$blockCount(null, '1w');
const avgHealth = await BlocksRepository.$getAvgBlockHealthPerPoolId(pool.id); const avgHealth = await BlocksRepository.$getAvgBlockHealthPerPoolId(pool.id);
const totalReward = await BlocksRepository.$getTotalRewardForPoolId(pool.id); const totalReward = await BlocksRepository.$getTotalRewardForPoolId(pool.id);
let currentEstimatedHashrate = 0; let currentEstimatedHashrate = 0;
try { try {
@ -235,7 +235,7 @@ class Mining {
const indexedTimestamp = await HashratesRepository.$getWeeklyHashrateTimestamps(); const indexedTimestamp = await HashratesRepository.$getWeeklyHashrateTimestamps();
const hashrates: any[] = []; const hashrates: any[] = [];
const lastMonday = new Date(now.setDate(now.getDate() - (now.getDay() + 6) % 7)); const lastMonday = new Date(now.setDate(now.getDate() - (now.getDay() + 6) % 7));
const lastMondayMidnight = this.getDateMidnight(lastMonday); const lastMondayMidnight = this.getDateMidnight(lastMonday);
let toTimestamp = lastMondayMidnight.getTime(); let toTimestamp = lastMondayMidnight.getTime();
@ -537,7 +537,7 @@ class Mining {
let totalInserted = 0; let totalInserted = 0;
try { try {
const prices: any[] = await PricesRepository.$getPricesTimesAndId(); const prices: any[] = await PricesRepository.$getPricesTimesAndId();
const blocksWithoutPrices: any[] = await BlocksRepository.$getBlocksWithoutPrice(); const blocksWithoutPrices: any[] = await BlocksRepository.$getBlocksWithoutPrice();
const blocksPrices: BlockPrice[] = []; const blocksPrices: BlockPrice[] = [];
@ -609,11 +609,11 @@ class Mining {
while (currentBlockHeight > 0) { while (currentBlockHeight > 0) {
const indexedBlocks = await BlocksRepository.$getBlocksMissingCoinStatsIndex( const indexedBlocks = await BlocksRepository.$getBlocksMissingCoinStatsIndex(
currentBlockHeight, currentBlockHeight - 10000); currentBlockHeight, currentBlockHeight - 10000);
for (const block of indexedBlocks) { for (const block of indexedBlocks) {
const txoutset = await bitcoinClient.getTxoutSetinfo('none', block.height); const txoutset = await bitcoinClient.getTxoutSetinfo('none', block.height);
await BlocksRepository.$updateCoinStatsIndexData(block.hash, txoutset.txouts, await BlocksRepository.$updateCoinStatsIndexData(block.hash, txoutset.txouts,
Math.round(txoutset.block_info.prevout_spent * 100000000)); Math.round(txoutset.block_info.prevout_spent * 100000000));
++totalIndexed; ++totalIndexed;
const elapsedSeconds = Math.max(1, new Date().getTime() / 1000 - timer); const elapsedSeconds = Math.max(1, new Date().getTime() / 1000 - timer);
@ -688,7 +688,7 @@ class Mining {
default: return 1 * scale; default: return 1 * scale;
} }
} }
// Finds the oldest block in a consecutive chain back from the tip // Finds the oldest block in a consecutive chain back from the tip
// assumes `blocks` is sorted in ascending height order // assumes `blocks` is sorted in ascending height order

View file

@ -1,10 +1,10 @@
import config from "../config"; import config from '../config';
import logger from "../logger"; import logger from '../logger';
import { MempoolTransactionExtended, TransactionStripped } from "../mempool.interfaces"; import { MempoolTransactionExtended, TransactionStripped } from '../mempool.interfaces';
import bitcoinApi from './bitcoin/bitcoin-api-factory'; import bitcoinApi from './bitcoin/bitcoin-api-factory';
import { IEsploraApi } from "./bitcoin/esplora-api.interface"; import { IEsploraApi } from './bitcoin/esplora-api.interface';
import { Common } from "./common"; import { Common } from './common';
import redisCache from "./redis-cache"; import redisCache from './redis-cache';
export interface RbfTransaction extends TransactionStripped { export interface RbfTransaction extends TransactionStripped {
rbf?: boolean; rbf?: boolean;

View file

@ -514,7 +514,7 @@ class StatisticsApi {
vsize_1600: completeVsizes[36], vsize_1600: completeVsizes[36],
vsize_1800: completeVsizes[37], vsize_1800: completeVsizes[37],
vsize_2000: completeVsizes[38], vsize_2000: completeVsizes[38],
} };
}); });
} }
} }

View file

@ -116,7 +116,7 @@ class TransactionUtils {
public extendMempoolTransaction(transaction: IEsploraApi.Transaction): MempoolTransactionExtended { public extendMempoolTransaction(transaction: IEsploraApi.Transaction): MempoolTransactionExtended {
const vsize = Math.ceil(transaction.weight / 4); const vsize = Math.ceil(transaction.weight / 4);
const fractionalVsize = (transaction.weight / 4); const fractionalVsize = (transaction.weight / 4);
let sigops = Common.isLiquid() ? 0 : (transaction.sigops != null ? transaction.sigops : this.countSigops(transaction)); const sigops = Common.isLiquid() ? 0 : (transaction.sigops != null ? transaction.sigops : this.countSigops(transaction));
// https://github.com/bitcoin/bitcoin/blob/e9262ea32a6e1d364fb7974844fadc36f931f8c6/src/policy/policy.cpp#L295-L298 // https://github.com/bitcoin/bitcoin/blob/e9262ea32a6e1d364fb7974844fadc36f931f8c6/src/policy/policy.cpp#L295-L298
const adjustedVsize = Math.max(fractionalVsize, sigops * 5); // adjusted vsize = Max(weight, sigops * bytes_per_sigop) / witness_scale_factor const adjustedVsize = Math.max(fractionalVsize, sigops * 5); // adjusted vsize = Max(weight, sigops * bytes_per_sigop) / witness_scale_factor
const feePerVbytes = (transaction.fee || 0) / fractionalVsize; const feePerVbytes = (transaction.fee || 0) / fractionalVsize;
@ -365,7 +365,7 @@ class TransactionUtils {
* the script item if it is a script spend. * the script item if it is a script spend.
*/ */
public witnessToP2TRScript(witness: string[]): string | null { public witnessToP2TRScript(witness: string[]): string | null {
if (witness.length < 2) return null; if (witness.length < 2) {return null;}
// Note: see BIP341 for parsing details of witness stack // Note: see BIP341 for parsing details of witness stack
// If there are at least two witness elements, and the first byte of the // If there are at least two witness elements, and the first byte of the
@ -375,7 +375,7 @@ class TransactionUtils {
// If there are at least two witness elements left, script path spending is used. // If there are at least two witness elements left, script path spending is used.
// Call the second-to-last stack element s, the script. // Call the second-to-last stack element s, the script.
// (Note: this phrasing from BIP341 assumes we've *removed* the annex from the stack) // (Note: this phrasing from BIP341 assumes we've *removed* the annex from the stack)
if (hasAnnex && witness.length < 3) return null; if (hasAnnex && witness.length < 3) {return null;}
const positionOfScript = hasAnnex ? witness.length - 3 : witness.length - 2; const positionOfScript = hasAnnex ? witness.length - 3 : witness.length - 2;
return witness[positionOfScript]; return witness[positionOfScript];
} }
@ -482,7 +482,7 @@ class TransactionUtils {
return 'unknown'; return 'unknown';
} }
} }
} }
export default new TransactionUtils(); export default new TransactionUtils();

View file

@ -18,7 +18,7 @@ if (parentPort) {
mempool.delete(uid); mempool.delete(uid);
}); });
} }
const { blocks, rates, clusters } = makeBlockTemplates(mempool); const { blocks, rates, clusters } = makeBlockTemplates(mempool);
// return the result to main thread. // return the result to main thread.
@ -38,7 +38,7 @@ function makeBlockTemplates(mempool: Map<number, CompactThreadTransaction>)
const auditPool: Map<number, AuditTransaction> = new Map(); const auditPool: Map<number, AuditTransaction> = new Map();
const mempoolArray: AuditTransaction[] = []; const mempoolArray: AuditTransaction[] = [];
const cpfpClusters: Map<number, number[]> = new Map(); const cpfpClusters: Map<number, number[]> = new Map();
mempool.forEach(tx => { mempool.forEach(tx => {
tx.dirty = false; tx.dirty = false;
// initializing everything up front helps V8 optimize property access later // initializing everything up front helps V8 optimize property access later
@ -85,7 +85,7 @@ function makeBlockTemplates(mempool: Map<number, CompactThreadTransaction>)
// (i.e. the package rooted in the transaction with the best ancestor score) // (i.e. the package rooted in the transaction with the best ancestor score)
const blocks: number[][] = []; const blocks: number[][] = [];
let blockWeight = 4000; let blockWeight = 4000;
let blockSigops = 0; const blockSigops = 0;
let transactions: AuditTransaction[] = []; let transactions: AuditTransaction[] = [];
const modified: PairingHeap<AuditTransaction> = new PairingHeap((a, b): boolean => { const modified: PairingHeap<AuditTransaction> = new PairingHeap((a, b): boolean => {
if (a.score === b.score) { if (a.score === b.score) {

View file

@ -1002,7 +1002,7 @@ class WebsocketHandler {
}); });
} }
} }
async handleNewBlock(block: BlockExtended, txIds: string[], transactions: MempoolTransactionExtended[]): Promise<void> { async handleNewBlock(block: BlockExtended, txIds: string[], transactions: MempoolTransactionExtended[]): Promise<void> {
if (!this.webSocketServers.length) { if (!this.webSocketServers.length) {
throw new Error('No WebSocket.Server have been set'); throw new Error('No WebSocket.Server have been set');
@ -1518,7 +1518,7 @@ class WebsocketHandler {
if (client['track-rbf']) { if (client['track-rbf']) {
numRbfSubs++; numRbfSubs++;
} }
}) });
} }
let count = 0; let count = 0;

View file

@ -398,7 +398,7 @@ class Config implements IConfig {
}); });
return next; return next;
}); });
} };
} }
export default new Config(); export default new Config();

View file

@ -46,7 +46,7 @@ class Indexer {
synced: indexes[indexName].synced, synced: indexes[indexName].synced,
best_block_height: indexes[indexName].best_block_height, best_block_height: indexes[indexName].best_block_height,
}; };
logger.info(`Core index '${indexName}' is ${indexes[indexName].synced ? 'synced' : 'not synced'}. Best block height is ${indexes[indexName].best_block_height}`); logger.info(`Core index '${indexName}' is ${indexes[indexName].synced ? 'synced' : 'not synced'}. Best block height is ${indexes[indexName].best_block_height}`);
updatedCoreIndexes.push(newState); updatedCoreIndexes.push(newState);
if (indexName === 'coinstatsindex' && newState.synced === true) { if (indexName === 'coinstatsindex' && newState.synced === true) {
@ -62,9 +62,9 @@ class Indexer {
/** /**
* Return the best block height if a core index is available, or 0 if not * Return the best block height if a core index is available, or 0 if not
* *
* @param name * @param name
* @returns * @returns
*/ */
public isCoreIndexReady(name: string): CoreIndex | null { public isCoreIndexReady(name: string): CoreIndex | null {
for (const index of this.coreIndexes) { for (const index of this.coreIndexes) {

View file

@ -36,7 +36,7 @@ class Logger {
mining: 'Mining', mining: 'Mining',
ln: 'Lightning', ln: 'Lightning',
goggles: 'Goggles', goggles: 'Goggles',
}; };
// @ts-ignore // @ts-ignore
public emerg: ((msg: string, tag?: string) => void); public emerg: ((msg: string, tag?: string) => void);
@ -86,7 +86,7 @@ class Logger {
private getNetwork(): string { private getNetwork(): string {
if (config.LIGHTNING.ENABLED) { if (config.LIGHTNING.ENABLED) {
return config.MEMPOOL.NETWORK === 'mainnet' ? 'lightning' : `${config.MEMPOOL.NETWORK}-lightning`; return config.MEMPOOL.NETWORK === 'mainnet' ? 'lightning' : `${config.MEMPOOL.NETWORK}-lightning`;
} }
if (config.MEMPOOL.NETWORK && config.MEMPOOL.NETWORK !== 'mainnet') { if (config.MEMPOOL.NETWORK && config.MEMPOOL.NETWORK !== 'mainnet') {
return config.MEMPOOL.NETWORK; return config.MEMPOOL.NETWORK;

View file

@ -51,12 +51,12 @@ class StatisticsReplication {
logger.info(`Statistics table is complete, no replication needed`, 'Replication'); logger.info(`Statistics table is complete, no replication needed`, 'Replication');
return; return;
} }
for (const interval of missingIntervals) { for (const interval of missingIntervals) {
logger.debug(`Missing ${missingStatistics[interval].size} statistics rows in '${interval}' timespan`, 'Replication'); logger.debug(`Missing ${missingStatistics[interval].size} statistics rows in '${interval}' timespan`, 'Replication');
} }
logger.debug(`Fetching ${missingIntervals.join(', ')} statistics endpoints from trusted servers to fill ${totalMissing} rows missing in statistics`, 'Replication'); logger.debug(`Fetching ${missingIntervals.join(', ')} statistics endpoints from trusted servers to fill ${totalMissing} rows missing in statistics`, 'Replication');
let totalSynced = 0; let totalSynced = 0;
let totalMissed = 0; let totalMissed = 0;
@ -75,15 +75,15 @@ class StatisticsReplication {
} }
private async $syncStatistics(interval: string, missingTimes: Set<number>): Promise<any> { private async $syncStatistics(interval: string, missingTimes: Set<number>): Promise<any> {
let success = false; let success = false;
let synced = 0; let synced = 0;
let missed = new Set(missingTimes); const missed = new Set(missingTimes);
const syncResult = await $sync(`/api/v1/statistics/${interval}`); const syncResult = await $sync(`/api/v1/statistics/${interval}`);
if (syncResult && syncResult.data?.length) { if (syncResult && syncResult.data?.length) {
success = true; success = true;
logger.info(`Fetched /api/v1/statistics/${interval} from ${syncResult.server}`); logger.info(`Fetched /api/v1/statistics/${interval} from ${syncResult.server}`);
for (const stat of syncResult.data) { for (const stat of syncResult.data) {
const time = this.roundToNearestStep(stat.added, steps[interval]); const time = this.roundToNearestStep(stat.added, steps[interval]);
if (missingTimes.has(time)) { if (missingTimes.has(time)) {
@ -129,7 +129,7 @@ class StatisticsReplication {
startTime < now - day * 30 ? [now - day * 90, now - day * 30, '3m' ] : null, // from 3 months ago to 1 month ago = 2 hours granularity startTime < now - day * 30 ? [now - day * 90, now - day * 30, '3m' ] : null, // from 3 months ago to 1 month ago = 2 hours granularity
startTime < now - day * 90 ? [now - day * 180, now - day * 90, '6m' ] : null, // from 6 months ago to 3 months ago = 3 hours granularity startTime < now - day * 90 ? [now - day * 180, now - day * 90, '6m' ] : null, // from 6 months ago to 3 months ago = 3 hours granularity
startTime < now - day * 180 ? [now - day * 365 * 2, now - day * 180, '2y' ] : null, // from 2 years ago to 6 months ago = 8 hours granularity startTime < now - day * 180 ? [now - day * 365 * 2, now - day * 180, '2y' ] : null, // from 2 years ago to 6 months ago = 8 hours granularity
startTime < now - day * 365 * 2 ? [startTime, now - day * 365 * 2, 'all'] : null, // from start of statistics to 2 years ago = 12 hours granularity startTime < now - day * 365 * 2 ? [startTime, now - day * 365 * 2, 'all'] : null, // from start of statistics to 2 years ago = 12 hours granularity
]; ];
for (const interval of intervals) { for (const interval of intervals) {
@ -138,7 +138,7 @@ class StatisticsReplication {
} }
missingStatistics[interval[2] as string] = await this.$getMissingStatisticsInterval(interval, startTime); missingStatistics[interval[2] as string] = await this.$getMissingStatisticsInterval(interval, startTime);
} }
return missingStatistics; return missingStatistics;
} catch (e: any) { } catch (e: any) {
logger.err(`Cannot fetch missing statistics times from db. Reason: ` + (e instanceof Error ? e.message : e)); logger.err(`Cannot fetch missing statistics times from db. Reason: ` + (e instanceof Error ? e.message : e));
@ -169,17 +169,17 @@ class StatisticsReplication {
if (timeSteps.length === 0) { if (timeSteps.length === 0) {
return new Set<number>(); return new Set<number>();
} }
const roundedTimesAlreadyHere: number[] = Array.from(new Set(rows.map(row => this.roundToNearestStep(row.added, step)))); const roundedTimesAlreadyHere: number[] = Array.from(new Set(rows.map(row => this.roundToNearestStep(row.added, step))));
const missingTimes = timeSteps.filter(time => !roundedTimesAlreadyHere.includes(time)).filter((time, i, arr) => { const missingTimes = timeSteps.filter(time => !roundedTimesAlreadyHere.includes(time)).filter((time, i, arr) => {
// Remove outsiders // Remove outsiders
if (i === 0) { if (i === 0) {
return arr[i + 1] === time + step return arr[i + 1] === time + step;
} else if (i === arr.length - 1) { } else if (i === arr.length - 1) {
return arr[i - 1] === time - step; return arr[i - 1] === time - step;
} }
return (arr[i + 1] === time + step) && (arr[i - 1] === time - step) return (arr[i + 1] === time + step) && (arr[i - 1] === time - step);
}); });
// Don't bother fetching if very few rows are missing // Don't bother fetching if very few rows are missing

View file

@ -14,7 +14,7 @@ export async function $sync(path): Promise<{ data?: any, exists: boolean, server
if (server === backendInfo.getBackendInfo().hostname) { if (server === backendInfo.getBackendInfo().hostname) {
continue; continue;
} }
try { try {
const result = await query(`https://${server}${path}`); const result = await query(`https://${server}${path}`);
if (result) { if (result) {

View file

@ -100,7 +100,7 @@ class AccelerationRepository {
SELECT *, UNIX_TIMESTAMP(requested) as requested_timestamp, UNIX_TIMESTAMP(added) as block_timestamp FROM accelerations SELECT *, UNIX_TIMESTAMP(requested) as requested_timestamp, UNIX_TIMESTAMP(added) as block_timestamp FROM accelerations
JOIN pools on pools.unique_id = accelerations.pool JOIN pools on pools.unique_id = accelerations.pool
`; `;
let params: any[] = []; const params: any[] = [];
let hasFilter = false; let hasFilter = false;
if (interval && height === null) { if (interval && height === null) {
@ -163,7 +163,7 @@ class AccelerationRepository {
SELECT SUM(boost_cost) as total_cost, COUNT(txid) as count FROM accelerations SELECT SUM(boost_cost) as total_cost, COUNT(txid) as count FROM accelerations
JOIN pools on pools.unique_id = accelerations.pool JOIN pools on pools.unique_id = accelerations.pool
`; `;
let params: any[] = []; const params: any[] = [];
let hasFilter = false; let hasFilter = false;
if (interval) { if (interval) {
@ -346,7 +346,7 @@ class AccelerationRepository {
const accelerationSummaries = accelerations.map(acc => ({ const accelerationSummaries = accelerations.map(acc => ({
...acc, ...acc,
pools: acc.pools, pools: acc.pools,
})) }));
for (const acc of accelerations) { for (const acc of accelerations) {
if (blockTxs[acc.txid] && acc.pools.includes(block.extras.pool.id)) { if (blockTxs[acc.txid] && acc.pools.includes(block.extras.pool.id)) {
const tx = blockTxs[acc.txid]; const tx = blockTxs[acc.txid];

View file

@ -94,7 +94,7 @@ class BlocksAuditRepositories {
JOIN blocks_templates ON blocks_templates.id = blocks_audits.hash JOIN blocks_templates ON blocks_templates.id = blocks_audits.hash
WHERE blocks_audits.hash = ? WHERE blocks_audits.hash = ?
`, [hash]); `, [hash]);
if (rows.length) { if (rows.length) {
rows[0].unseenTxs = JSON.parse(rows[0].unseenTxs); rows[0].unseenTxs = JSON.parse(rows[0].unseenTxs);
rows[0].missingTxs = JSON.parse(rows[0].missingTxs); rows[0].missingTxs = JSON.parse(rows[0].missingTxs);

View file

@ -217,9 +217,9 @@ class BlocksRepository {
/** /**
* Save newly indexed data from core coinstatsindex * Save newly indexed data from core coinstatsindex
* *
* @param utxoSetSize * @param utxoSetSize
* @param totalInputAmt * @param totalInputAmt
*/ */
public async $updateCoinStatsIndexData(blockHash: string, utxoSetSize: number, public async $updateCoinStatsIndexData(blockHash: string, utxoSetSize: number,
totalInputAmt: number totalInputAmt: number
@ -245,9 +245,9 @@ class BlocksRepository {
/** /**
* Update missing fee amounts fields * Update missing fee amounts fields
* *
* @param blockHash * @param blockHash
* @param feeAmtPercentiles * @param feeAmtPercentiles
* @param medianFeeAmt * @param medianFeeAmt
*/ */
public async $updateFeeAmounts(blockHash: string, feeAmtPercentiles, medianFeeAmt) : Promise<void> { public async $updateFeeAmounts(blockHash: string, feeAmtPercentiles, medianFeeAmt) : Promise<void> {
try { try {
@ -275,7 +275,7 @@ class BlocksRepository {
// Ensure startHeight is the lower value and endHeight is the higher value // Ensure startHeight is the lower value and endHeight is the higher value
const minHeight = Math.min(startHeight, endHeight); const minHeight = Math.min(startHeight, endHeight);
const maxHeight = Math.max(startHeight, endHeight); const maxHeight = Math.max(startHeight, endHeight);
if (minHeight === maxHeight) { if (minHeight === maxHeight) {
return []; return [];
} }
@ -410,7 +410,7 @@ class BlocksRepository {
*/ */
public async $blockCountBetweenHeight(startHeight: number, endHeight: number): Promise<number> { public async $blockCountBetweenHeight(startHeight: number, endHeight: number): Promise<number> {
const params: any[] = []; const params: any[] = [];
let query = `SELECT count(height) as blockCount const query = `SELECT count(height) as blockCount
FROM blocks FROM blocks
WHERE height <= ${startHeight} AND height >= ${endHeight} AND stale = 0`; WHERE height <= ${startHeight} AND height >= ${endHeight} AND stale = 0`;
@ -1028,9 +1028,9 @@ class BlocksRepository {
/** /**
* Save indexed median fee to avoid recomputing it later * Save indexed median fee to avoid recomputing it later
* *
* @param id * @param id
* @param feePercentiles * @param feePercentiles
*/ */
public async $saveFeePercentilesForBlockId(id: string, feePercentiles: number[]): Promise<void> { public async $saveFeePercentilesForBlockId(id: string, feePercentiles: number[]): Promise<void> {
try { try {
@ -1047,9 +1047,9 @@ class BlocksRepository {
/** /**
* Save indexed effective fee statistics * Save indexed effective fee statistics
* *
* @param id * @param id
* @param feeStats * @param feeStats
*/ */
public async $saveEffectiveFeeStats(id: string, feeStats: EffectiveFeeStats): Promise<void> { public async $saveEffectiveFeeStats(id: string, feeStats: EffectiveFeeStats): Promise<void> {
try { try {
@ -1066,7 +1066,7 @@ class BlocksRepository {
/** /**
* Save coinbase addresses * Save coinbase addresses
* *
* @param id * @param id
* @param addresses * @param addresses
*/ */
@ -1085,7 +1085,7 @@ class BlocksRepository {
/** /**
* Save pool * Save pool
* *
* @param id * @param id
* @param poolId * @param poolId
*/ */
@ -1104,8 +1104,8 @@ class BlocksRepository {
/** /**
* Save block first seen time * Save block first seen time
* *
* @param id * @param id
*/ */
public async $saveFirstSeenTime(id: string, firstSeen: number): Promise<void> { public async $saveFirstSeenTime(id: string, firstSeen: number): Promise<void> {
try { try {
@ -1122,7 +1122,7 @@ class BlocksRepository {
/** /**
* Change which block at a height belongs to the canonical chain * Change which block at a height belongs to the canonical chain
* *
* @param hash * @param hash
* @param height * @param height
*/ */
@ -1151,8 +1151,8 @@ class BlocksRepository {
/** /**
* Convert a mysql row block into a BlockExtended. Note that you * Convert a mysql row block into a BlockExtended. Note that you
* must provide the correct field into dbBlk object param * must provide the correct field into dbBlk object param
* *
* @param dbBlk * @param dbBlk
*/ */
private async formatDbBlockIntoExtendedBlock(dbBlk: DatabaseBlock): Promise<BlockExtended> { private async formatDbBlockIntoExtendedBlock(dbBlk: DatabaseBlock): Promise<BlockExtended> {
const blk: Partial<BlockExtended> = {}; const blk: Partial<BlockExtended> = {};

View file

@ -154,8 +154,8 @@ class BlocksSummariesRepository {
/** /**
* Get the fee percentiles if the block has already been indexed, [] otherwise * Get the fee percentiles if the block has already been indexed, [] otherwise
* *
* @param id * @param id
*/ */
public async $getFeePercentilesByBlockId(id: string): Promise<number[] | null> { public async $getFeePercentilesByBlockId(id: string): Promise<number[] | null> {
try { try {

View file

@ -215,7 +215,7 @@ class HashratesRepository {
logger.err('Cannot delete latest hashrates data points. Reason: ' + (e instanceof Error ? e.message : e), logger.tags.mining); logger.err('Cannot delete latest hashrates data points. Reason: ' + (e instanceof Error ? e.message : e), logger.tags.mining);
} }
} }
/** /**
* Delete hashrates from the database from timestamp * Delete hashrates from the database from timestamp
*/ */

View file

@ -149,8 +149,8 @@ class PoolsRepository {
/** /**
* Insert a new mining pool in the database * Insert a new mining pool in the database
* *
* @param pool * @param pool
*/ */
public async $insertNewMiningPool(pool: any, slug: string): Promise<void> { public async $insertNewMiningPool(pool: any, slug: string): Promise<void> {
try { try {
@ -166,10 +166,10 @@ class PoolsRepository {
/** /**
* Rename an existing mining pool * Rename an existing mining pool
* *
* @param dbId * @param dbId
* @param newSlug * @param newSlug
* @param newName * @param newName
*/ */
public async $renameMiningPool(dbId: number, newSlug: string, newName: string): Promise<void> { public async $renameMiningPool(dbId: number, newSlug: string, newName: string): Promise<void> {
try { try {
@ -186,9 +186,9 @@ class PoolsRepository {
/** /**
* Update an exisiting mining pool link * Update an exisiting mining pool link
* *
* @param dbId * @param dbId
* @param newLink * @param newLink
*/ */
public async $updateMiningPoolLink(dbId: number, newLink: string): Promise<void> { public async $updateMiningPoolLink(dbId: number, newLink: string): Promise<void> {
try { try {
@ -206,10 +206,10 @@ class PoolsRepository {
/** /**
* Update an existing mining pool addresses or coinbase tags * Update an existing mining pool addresses or coinbase tags
* *
* @param dbId * @param dbId
* @param addresses * @param addresses
* @param regexes * @param regexes
*/ */
public async $updateMiningPoolTags(dbId: number, addresses: string, regexes: string): Promise<void> { public async $updateMiningPoolTags(dbId: number, addresses: string, regexes: string): Promise<void> {
try { try {

View file

@ -179,7 +179,7 @@ class PricesRepository {
prices[currency] = 0; prices[currency] = 0;
} }
} }
try { try {
if (!config.FIAT_PRICE.API_KEY) { // Store only the 7 main currencies if (!config.FIAT_PRICE.API_KEY) { // Store only the 7 main currencies
await DB.query(` await DB.query(`
@ -191,8 +191,8 @@ class PricesRepository {
await DB.query(` await DB.query(`
INSERT INTO prices(time, USD, EUR, GBP, CAD, CHF, AUD, JPY, BGN, BRL, CNY, CZK, DKK, HKD, HRK, HUF, IDR, ILS, INR, ISK, KRW, MXN, MYR, NOK, NZD, PHP, PLN, RON, RUB, SEK, SGD, THB, TRY, ZAR) INSERT INTO prices(time, USD, EUR, GBP, CAD, CHF, AUD, JPY, BGN, BRL, CNY, CZK, DKK, HKD, HRK, HUF, IDR, ILS, INR, ISK, KRW, MXN, MYR, NOK, NZD, PHP, PLN, RON, RUB, SEK, SGD, THB, TRY, ZAR)
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ? , ?, ?, ?, ?, ?, ?, ?, ? , ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? , ? )`, VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ? , ?, ?, ?, ?, ?, ?, ?, ? , ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? , ? )`,
[time, prices.USD, prices.EUR, prices.GBP, prices.CAD, prices.CHF, prices.AUD, prices.JPY, prices.BGN, prices.BRL, prices.CNY, prices.CZK, prices.DKK, [time, prices.USD, prices.EUR, prices.GBP, prices.CAD, prices.CHF, prices.AUD, prices.JPY, prices.BGN, prices.BRL, prices.CNY, prices.CZK, prices.DKK,
prices.HKD, prices.HRK, prices.HUF, prices.IDR, prices.ILS, prices.INR, prices.ISK, prices.KRW, prices.MXN, prices.MYR, prices.NOK, prices.NZD, prices.HKD, prices.HRK, prices.HUF, prices.IDR, prices.ILS, prices.INR, prices.ISK, prices.KRW, prices.MXN, prices.MYR, prices.NOK, prices.NZD,
prices.PHP, prices.PLN, prices.RON, prices.RUB, prices.SEK, prices.SGD, prices.THB, prices.TRY, prices.ZAR] prices.PHP, prices.PLN, prices.RON, prices.RUB, prices.SEK, prices.SGD, prices.THB, prices.TRY, prices.ZAR]
); );
} }
@ -341,8 +341,8 @@ class PricesRepository {
`); `);
if (!Array.isArray(latestPrices)) { if (!Array.isArray(latestPrices)) {
throw Error(`Cannot get single historical price from the database`); throw Error(`Cannot get single historical price from the database`);
} }
// Compute fiat exchange rates // Compute fiat exchange rates
let latestPrice = latestPrices[0] as ApiPrice; let latestPrice = latestPrices[0] as ApiPrice;
if (!latestPrice || latestPrice.USD === -1) { if (!latestPrice || latestPrice.USD === -1) {
@ -350,8 +350,8 @@ class PricesRepository {
} }
const computeFx = (usd: number, other: number): number => usd <= 0.05 ? 0 : Math.round(Math.max(other, 0) / usd * 100) / 100; const computeFx = (usd: number, other: number): number => usd <= 0.05 ? 0 : Math.round(Math.max(other, 0) / usd * 100) / 100;
const exchangeRates: ExchangeRates = config.FIAT_PRICE.API_KEY ? const exchangeRates: ExchangeRates = config.FIAT_PRICE.API_KEY ?
{ {
USDEUR: computeFx(latestPrice.USD, latestPrice.EUR), USDEUR: computeFx(latestPrice.USD, latestPrice.EUR),
USDGBP: computeFx(latestPrice.USD, latestPrice.GBP), USDGBP: computeFx(latestPrice.USD, latestPrice.GBP),
@ -446,10 +446,10 @@ class PricesRepository {
latestPrice = priceUpdater.getEmptyPricesObj(); latestPrice = priceUpdater.getEmptyPricesObj();
} }
const computeFx = (usd: number, other: number): number => const computeFx = (usd: number, other: number): number =>
usd <= 0 ? 0 : Math.round(Math.max(other, 0) / usd * 100) / 100; usd <= 0 ? 0 : Math.round(Math.max(other, 0) / usd * 100) / 100;
const exchangeRates: ExchangeRates = config.FIAT_PRICE.API_KEY ? const exchangeRates: ExchangeRates = config.FIAT_PRICE.API_KEY ?
{ {
USDEUR: computeFx(latestPrice.USD, latestPrice.EUR), USDEUR: computeFx(latestPrice.USD, latestPrice.EUR),
USDGBP: computeFx(latestPrice.USD, latestPrice.GBP), USDGBP: computeFx(latestPrice.USD, latestPrice.GBP),

View file

@ -1,61 +1,61 @@
var commands = require('./commands') const commands = require('./commands');
var rpc = require('./jsonrpc') const rpc = require('./jsonrpc');
// ===----------------------------------------------------------------------===// // ===----------------------------------------------------------------------===//
// JsonRPC // JsonRPC
// ===----------------------------------------------------------------------===// // ===----------------------------------------------------------------------===//
function Client (opts) { function Client (opts) {
// @ts-ignore // @ts-ignore
this.rpc = new rpc.JsonRPC(opts) this.rpc = new rpc.JsonRPC(opts);
} }
// ===----------------------------------------------------------------------===// // ===----------------------------------------------------------------------===//
// cmd // cmd
// ===----------------------------------------------------------------------===// // ===----------------------------------------------------------------------===//
Client.prototype.cmd = function () { Client.prototype.cmd = function () {
var args = [].slice.call(arguments) const args = [].slice.call(arguments);
var cmd = args.shift() const cmd = args.shift();
callRpc(cmd, args, this.rpc) callRpc(cmd, args, this.rpc);
} };
// ===----------------------------------------------------------------------===// // ===----------------------------------------------------------------------===//
// callRpc // callRpc
// ===----------------------------------------------------------------------===// // ===----------------------------------------------------------------------===//
function callRpc (cmd, args, rpc) { function callRpc (cmd, args, rpc) {
var fn = args[args.length - 1] let fn = args[args.length - 1];
// If the last argument is a callback, pop it from the args list // If the last argument is a callback, pop it from the args list
if (typeof fn === 'function') { if (typeof fn === 'function') {
args.pop() args.pop();
} else { } else {
fn = function () {} fn = function () {};
} }
return rpc.call(cmd, args, function () { return rpc.call(cmd, args, function () {
var args = [].slice.call(arguments) const args = [].slice.call(arguments);
// @ts-ignore // @ts-ignore
args.unshift(null) args.unshift(null);
// @ts-ignore // @ts-ignore
fn.apply(this, args) fn.apply(this, args);
}, function (err) { }, function (err) {
fn(err) fn(err);
}) });
} }
// ===----------------------------------------------------------------------===// // ===----------------------------------------------------------------------===//
// Initialize wrappers // Initialize wrappers
// ===----------------------------------------------------------------------===// // ===----------------------------------------------------------------------===//
(function () { (function () {
for (var protoFn in commands) { for (const protoFn in commands) {
(function (protoFn) { (function (protoFn) {
Client.prototype[protoFn] = function () { Client.prototype[protoFn] = function () {
var args = [].slice.call(arguments) const args = [].slice.call(arguments);
return callRpc(commands[protoFn], args, this.rpc) return callRpc(commands[protoFn], args, this.rpc);
} };
})(protoFn) })(protoFn);
} }
})() })();
// Export! // Export!
module.exports.Client = Client; module.exports.Client = Client;

View file

@ -1,43 +1,43 @@
var http = require('http') const http = require('http');
var https = require('https') const https = require('https');
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
var JsonRPC = function (opts) { const JsonRPC = function (opts) {
// @ts-ignore // @ts-ignore
this.opts = opts || {} this.opts = opts || {};
// @ts-ignore // @ts-ignore
this.http = this.opts.ssl ? https : http this.http = this.opts.ssl ? https : http;
} };
JsonRPC.prototype.call = function (method, params) { JsonRPC.prototype.call = function (method, params) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
var time = Date.now() const time = Date.now();
var requestJSON let requestJSON;
if (Array.isArray(method)) { if (Array.isArray(method)) {
// multiple rpc batch call // multiple rpc batch call
requestJSON = [] requestJSON = [];
method.forEach(function (batchCall, i) { method.forEach(function (batchCall, i) {
requestJSON.push({ requestJSON.push({
id: time + '-' + i, id: time + '-' + i,
method: batchCall.method, method: batchCall.method,
params: batchCall.params params: batchCall.params
}) });
}) });
} else { } else {
// single rpc call // single rpc call
requestJSON = { requestJSON = {
id: time, id: time,
method: method, method: method,
params: params params: params
} };
} }
// First we encode the request into JSON // First we encode the request into JSON
requestJSON = JSON.stringify(requestJSON) requestJSON = JSON.stringify(requestJSON);
// prepare request options // prepare request options
var requestOptions = { const requestOptions = {
host: this.opts.host || 'localhost', host: this.opts.host || 'localhost',
port: this.opts.port || 8332, port: this.opts.port || 8332,
method: 'POST', method: 'POST',
@ -48,11 +48,11 @@ JsonRPC.prototype.call = function (method, params) {
}, },
agent: false, agent: false,
rejectUnauthorized: this.opts.ssl && this.opts.sslStrict !== false rejectUnauthorized: this.opts.ssl && this.opts.sslStrict !== false
} };
if (this.opts.ssl && this.opts.sslCa) { if (this.opts.ssl && this.opts.sslCa) {
// @ts-ignore // @ts-ignore
requestOptions.ca = this.opts.sslCa requestOptions.ca = this.opts.sslCa;
} }
// use HTTP auth if user and password set // use HTTP auth if user and password set
@ -64,61 +64,61 @@ JsonRPC.prototype.call = function (method, params) {
requestOptions.auth = this.cachedCookie; requestOptions.auth = this.cachedCookie;
} else if (this.opts.user && this.opts.pass) { } else if (this.opts.user && this.opts.pass) {
// @ts-ignore // @ts-ignore
requestOptions.auth = this.opts.user + ':' + this.opts.pass requestOptions.auth = this.opts.user + ':' + this.opts.pass;
} }
// Now we'll make a request to the server // Now we'll make a request to the server
var cbCalled = false let cbCalled = false;
var request = this.http.request(requestOptions) const request = this.http.request(requestOptions);
// start request timeout timer // start request timeout timer
var reqTimeout = setTimeout(function () { const reqTimeout = setTimeout(function () {
if (cbCalled) return if (cbCalled) {return;}
cbCalled = true cbCalled = true;
request.abort() request.abort();
var err = new Error('ETIMEDOUT') const err = new Error('ETIMEDOUT');
// @ts-ignore // @ts-ignore
err.code = 'ETIMEDOUT' err.code = 'ETIMEDOUT';
reject(err) reject(err);
}, this.opts.timeout || 30000) }, this.opts.timeout || 30000);
// set additional timeout on socket in case of remote freeze after sending headers // set additional timeout on socket in case of remote freeze after sending headers
request.setTimeout(this.opts.timeout || 30000, function () { request.setTimeout(this.opts.timeout || 30000, function () {
if (cbCalled) return if (cbCalled) {return;}
cbCalled = true cbCalled = true;
request.abort() request.abort();
var err = new Error('ESOCKETTIMEDOUT') const err = new Error('ESOCKETTIMEDOUT');
// @ts-ignore // @ts-ignore
err.code = 'ESOCKETTIMEDOUT' err.code = 'ESOCKETTIMEDOUT';
reject(err) reject(err);
}) });
request.on('error', function (err) { request.on('error', function (err) {
if (cbCalled) return if (cbCalled) {return;}
cbCalled = true cbCalled = true;
clearTimeout(reqTimeout) clearTimeout(reqTimeout);
reject(err) reject(err);
}) });
request.on('response', (response) => { request.on('response', (response) => {
clearTimeout(reqTimeout) clearTimeout(reqTimeout);
// We need to buffer the response chunks in a nonblocking way. // We need to buffer the response chunks in a nonblocking way.
var buffer = '' let buffer = '';
response.on('data', function (chunk) { response.on('data', function (chunk) {
buffer = buffer + chunk buffer = buffer + chunk;
}) });
// When all the responses are finished, we decode the JSON and // When all the responses are finished, we decode the JSON and
// depending on whether it's got a result or an error, we call // depending on whether it's got a result or an error, we call
// emitSuccess or emitError on the promise. // emitSuccess or emitError on the promise.
response.on('end', () => { response.on('end', () => {
var err let err;
if (cbCalled) return if (cbCalled) {return;}
cbCalled = true cbCalled = true;
try { try {
var decoded = JSON.parse(buffer) var decoded = JSON.parse(buffer);
} catch (e) { } catch (e) {
// if we authenticated using a cookie and it failed, read the cookie file again // if we authenticated using a cookie and it failed, read the cookie file again
if ( if (
@ -129,19 +129,19 @@ JsonRPC.prototype.call = function (method, params) {
} }
if (response.statusCode !== 200) { if (response.statusCode !== 200) {
err = new Error('Invalid params, response status code: ' + response.statusCode) err = new Error('Invalid params, response status code: ' + response.statusCode);
err.code = -32602 err.code = -32602;
reject(err) reject(err);
} else { } else {
err = new Error('Problem parsing JSON response from server') err = new Error('Problem parsing JSON response from server');
err.code = -32603 err.code = -32603;
reject(err) reject(err);
} }
return return;
} }
if (!Array.isArray(decoded)) { if (!Array.isArray(decoded)) {
decoded = [decoded] decoded = [decoded];
} }
// iterate over each response, normally there will be just one // iterate over each response, normally there will be just one
@ -149,29 +149,29 @@ JsonRPC.prototype.call = function (method, params) {
decoded.forEach(function (decodedResponse, i) { decoded.forEach(function (decodedResponse, i) {
if (decodedResponse.hasOwnProperty('error') && decodedResponse.error != null) { if (decodedResponse.hasOwnProperty('error') && decodedResponse.error != null) {
if (reject) { if (reject) {
err = new Error(decodedResponse.error.message || '') err = new Error(decodedResponse.error.message || '');
if (decodedResponse.error.code) { if (decodedResponse.error.code) {
err.code = decodedResponse.error.code err.code = decodedResponse.error.code;
} }
reject(err) reject(err);
} }
} else if (decodedResponse.hasOwnProperty('result')) { } else if (decodedResponse.hasOwnProperty('result')) {
// @ts-ignore // @ts-ignore
resolve(decodedResponse.result, response.headers) resolve(decodedResponse.result, response.headers);
} else { } else {
if (reject) { if (reject) {
err = new Error(decodedResponse.error.message || '') err = new Error(decodedResponse.error.message || '');
if (decodedResponse.error.code) { if (decodedResponse.error.code) {
err.code = decodedResponse.error.code err.code = decodedResponse.error.code;
} }
reject(err) reject(err);
} }
} }
}) });
}) });
}) });
request.end(requestJSON); request.end(requestJSON);
}); });
} };
module.exports.JsonRPC = JsonRPC module.exports.JsonRPC = JsonRPC;

View file

@ -449,7 +449,7 @@ class ForensicsService {
const initiatorSide = initiator === 'remote' ? prevRemote : prevLocal; const initiatorSide = initiator === 'remote' ? prevRemote : prevLocal;
prevChannel.closed_by = prevChannel[`node${initiatorSide}_public_key`]; prevChannel.closed_by = prevChannel[`node${initiatorSide}_public_key`];
} }
// save changes to the closing channel // save changes to the closing channel
await channelsApi.$updateClosingInfo(prevChannel); await channelsApi.$updateClosingInfo(prevChannel);
} else { } else {

View file

@ -47,7 +47,7 @@ class NetworkSyncService {
await this.$lookUpCreationDateFromChain(); await this.$lookUpCreationDateFromChain();
await this.$updateNodeFirstSeen(); await this.$updateNodeFirstSeen();
await this.$scanForClosedChannels(); await this.$scanForClosedChannels();
if (config.MEMPOOL.BACKEND === 'esplora') { if (config.MEMPOOL.BACKEND === 'esplora') {
// run forensics on new channels only // run forensics on new channels only
await forensicsService.$runClosedChannelsForensics(true); await forensicsService.$runClosedChannelsForensics(true);
@ -226,7 +226,7 @@ class NetworkSyncService {
if (channels.length > 0) { if (channels.length > 0) {
logger.debug(`Updated ${channels.length} channels' creation date`, logger.tags.ln); logger.debug(`Updated ${channels.length} channels' creation date`, logger.tags.ln);
} }
} catch (e) { } catch (e) {
logger.err(`$lookUpCreationDateFromChain() error: ${e instanceof Error ? e.message : e}`, logger.tags.ln); logger.err(`$lookUpCreationDateFromChain() error: ${e instanceof Error ? e.message : e}`, logger.tags.ln);
} }

View file

@ -6,7 +6,7 @@ import logger from '../../../logger';
const fsPromises = promises; const fsPromises = promises;
const BLOCKS_CACHE_MAX_SIZE = 100; const BLOCKS_CACHE_MAX_SIZE = 100;
const CACHE_FILE_NAME = config.MEMPOOL.CACHE_DIR + '/ln-funding-txs-cache.json'; const CACHE_FILE_NAME = config.MEMPOOL.CACHE_DIR + '/ln-funding-txs-cache.json';
class FundingTxFetcher { class FundingTxFetcher {
@ -33,7 +33,7 @@ class FundingTxFetcher {
return; return;
} }
this.running = true; this.running = true;
const globalTimer = new Date().getTime() / 1000; const globalTimer = new Date().getTime() / 1000;
let cacheTimer = new Date().getTime() / 1000; let cacheTimer = new Date().getTime() / 1000;
let loggerTimer = new Date().getTime() / 1000; let loggerTimer = new Date().getTime() / 1000;
@ -70,7 +70,7 @@ class FundingTxFetcher {
this.running = false; this.running = false;
} }
public async $fetchChannelOpenTx(channelId: string): Promise<{timestamp: number, txid: string, value: number} | null> { public async $fetchChannelOpenTx(channelId: string): Promise<{timestamp: number, txid: string, value: number} | null> {
channelId = Common.channelIntegerIdToShortId(channelId); channelId = Common.channelIntegerIdToShortId(channelId);

View file

@ -60,13 +60,13 @@ export async function $lookupNodeLocation(): Promise<void> {
if (city && (asn || isp)) { if (city && (asn || isp)) {
const query = ` const query = `
UPDATE nodes SET UPDATE nodes SET
as_number = ?, as_number = ?,
city_id = ?, city_id = ?,
country_id = ?, country_id = ?,
subdivision_id = ?, subdivision_id = ?,
longitude = ?, longitude = ?,
latitude = ?, latitude = ?,
accuracy_radius = ? accuracy_radius = ?
WHERE public_key = ? WHERE public_key = ?
`; `;

View file

@ -99,7 +99,7 @@ class LightningStatsImporter {
const feeRates: number[] = []; const feeRates: number[] = [];
const baseFees: number[] = []; const baseFees: number[] = [];
const alreadyCountedChannels = {}; const alreadyCountedChannels = {};
const [channelsInDbRaw]: any[] = await DB.query(`SELECT short_id FROM channels`); const [channelsInDbRaw]: any[] = await DB.query(`SELECT short_id FROM channels`);
const channelsInDb = {}; const channelsInDb = {};
for (const channel of channelsInDbRaw) { for (const channel of channelsInDbRaw) {
@ -145,7 +145,7 @@ class LightningStatsImporter {
channels: 0, channels: 0,
}; };
} }
if (!alreadyCountedChannels[short_id]) { if (!alreadyCountedChannels[short_id]) {
capacity += Math.round(tx.value * 100000000); capacity += Math.round(tx.value * 100000000);
capacities.push(Math.round(tx.value * 100000000)); capacities.push(Math.round(tx.value * 100000000));
@ -162,7 +162,7 @@ class LightningStatsImporter {
if (policy && parseInt(policy.fee_rate_milli_msat, 10) < 5000) { if (policy && parseInt(policy.fee_rate_milli_msat, 10) < 5000) {
avgFeeRate += parseInt(policy.fee_rate_milli_msat, 10); avgFeeRate += parseInt(policy.fee_rate_milli_msat, 10);
feeRates.push(parseInt(policy.fee_rate_milli_msat, 10)); feeRates.push(parseInt(policy.fee_rate_milli_msat, 10));
} }
if (policy && parseInt(policy.fee_base_msat, 10) < 5000) { if (policy && parseInt(policy.fee_base_msat, 10) < 5000) {
avgBaseFee += parseInt(policy.fee_base_msat, 10); avgBaseFee += parseInt(policy.fee_base_msat, 10);
baseFees.push(parseInt(policy.fee_base_msat, 10)); baseFees.push(parseInt(policy.fee_base_msat, 10));
@ -388,7 +388,7 @@ class LightningStatsImporter {
totalProcessed++; totalProcessed++;
continue; continue;
} }
if (this.isIncorrectSnapshot(timestamp, graph)) { if (this.isIncorrectSnapshot(timestamp, graph)) {
logger.debug(`Ignoring ${this.topologiesFolder}/${filename}, because we defined it as an incorrect snapshot`); logger.debug(`Ignoring ${this.topologiesFolder}/${filename}, because we defined it as an incorrect snapshot`);
++totalProcessed; ++totalProcessed;
@ -399,7 +399,7 @@ class LightningStatsImporter {
logger.info(`Founds a topology file that we did not import. Importing historical lightning stats now.`, logger.tags.ln); logger.info(`Founds a topology file that we did not import. Importing historical lightning stats now.`, logger.tags.ln);
logStarted = true; logStarted = true;
} }
const datestr = `${new Date(timestamp * 1000).toUTCString()} (${timestamp})`; const datestr = `${new Date(timestamp * 1000).toUTCString()} (${timestamp})`;
logger.debug(`${datestr}: Found ${graph.nodes.length} nodes and ${graph.edges.length} channels`, logger.tags.ln); logger.debug(`${datestr}: Found ${graph.nodes.length} nodes and ${graph.edges.length} channels`, logger.tags.ln);
@ -475,7 +475,7 @@ class LightningStatsImporter {
fee_rate_milli_msat: edge.fee_proportional_millionths, fee_rate_milli_msat: edge.fee_proportional_millionths,
max_htlc_msat: edge.htlc_maximum_msat, max_htlc_msat: edge.htlc_maximum_msat,
last_update: edge.timestamp, last_update: edge.timestamp,
disabled: false, disabled: false,
}, },
node2_policy: null, node2_policy: null,
}); });
@ -545,7 +545,7 @@ class LightningStatsImporter {
// UNIX_TIMESTAMP(added) >= 1591142400 AND UNIX_TIMESTAMP(added) <= 1592006400 OR // UNIX_TIMESTAMP(added) >= 1591142400 AND UNIX_TIMESTAMP(added) <= 1592006400 OR
// UNIX_TIMESTAMP(added) >= 1632787200 AND UNIX_TIMESTAMP(added) <= 1633564800 OR // UNIX_TIMESTAMP(added) >= 1632787200 AND UNIX_TIMESTAMP(added) <= 1633564800 OR
// UNIX_TIMESTAMP(added) >= 1634256000 AND UNIX_TIMESTAMP(added) <= 1645401600 OR // UNIX_TIMESTAMP(added) >= 1634256000 AND UNIX_TIMESTAMP(added) <= 1645401600 OR
// UNIX_TIMESTAMP(added) >= 1654992000 AND UNIX_TIMESTAMP(added) <= 1661472000 // UNIX_TIMESTAMP(added) >= 1654992000 AND UNIX_TIMESTAMP(added) <= 1661472000
// ) // )
} }
} }

View file

@ -69,7 +69,7 @@ class KrakenApi implements PriceFeed {
// CHF weekly price history goes back to timestamp 1575504000 (December 5, 2019) // CHF weekly price history goes back to timestamp 1575504000 (December 5, 2019)
// AUD weekly price history goes back to timestamp 1591833600 (June 11, 2020) // AUD weekly price history goes back to timestamp 1591833600 (June 11, 2020)
let priceHistory: any = {}; // map: timestamp -> Prices const priceHistory: any = {}; // map: timestamp -> Prices
for (const currency of this.currencies) { for (const currency of this.currencies) {
const response = await query(this.urlHist.replace('{GRANULARITY}', '10080') + currency); const response = await query(this.urlHist.replace('{GRANULARITY}', '10080') + currency);

View file

@ -432,7 +432,7 @@ class PriceUpdater {
this.additionalCurrenciesHistoryRunning = true; this.additionalCurrenciesHistoryRunning = true;
logger.debug(`Inserting missing historical conversion rates using conversions API to fill ${priceTimesToFill.length} rows`, logger.tags.mining); logger.debug(`Inserting missing historical conversion rates using conversions API to fill ${priceTimesToFill.length} rows`, logger.tags.mining);
let conversionRates: { [timestamp: number]: ConversionRates } = {}; const conversionRates: { [timestamp: number]: ConversionRates } = {};
let totalInserted = 0; let totalInserted = 0;
for (let i = 0; i < priceTimesToFill.length; i++) { for (let i = 0; i < priceTimesToFill.length; i++) {
@ -464,7 +464,7 @@ class PriceUpdater {
} }
const prices: ApiPrice = this.getEmptyPricesObj(); const prices: ApiPrice = this.getEmptyPricesObj();
let willInsert = false; let willInsert = false;
for (const conversionCurrency of this.newCurrencies.concat(missingLegacyCurrencies)) { for (const conversionCurrency of this.newCurrencies.concat(missingLegacyCurrencies)) {
if (conversionRates[yearMonthTimestamp][conversionCurrency] > 0 && priceTime.USD * conversionRates[yearMonthTimestamp][conversionCurrency] < MAX_PRICES[conversionCurrency]) { if (conversionRates[yearMonthTimestamp][conversionCurrency] > 0 && priceTime.USD * conversionRates[yearMonthTimestamp][conversionCurrency] < MAX_PRICES[conversionCurrency]) {
@ -474,7 +474,7 @@ class PriceUpdater {
prices[conversionCurrency] = 0; prices[conversionCurrency] = 0;
} }
} }
if (willInsert) { if (willInsert) {
await PricesRepository.$saveAdditionalCurrencyPrices(priceTime.time, prices, missingLegacyCurrencies); await PricesRepository.$saveAdditionalCurrencyPrices(priceTime.time, prices, missingLegacyCurrencies);
++totalInserted; ++totalInserted;

View file

@ -204,7 +204,7 @@ export function getVarIntLength(n: number): number {
/** Extracts miner names from a DATUM coinbase transaction */ /** Extracts miner names from a DATUM coinbase transaction */
export function parseDATUMTemplateCreator(coinbaseRaw: string): string[] | null { export function parseDATUMTemplateCreator(coinbaseRaw: string): string[] | null {
let bytes: number[] = []; const bytes: number[] = [];
for (let c = 0; c < coinbaseRaw.length; c += 2) { for (let c = 0; c < coinbaseRaw.length; c += 2) {
bytes.push(parseInt(coinbaseRaw.slice(c, c + 2), 16)); bytes.push(parseInt(coinbaseRaw.slice(c, c + 2), 16));
} }

View file

@ -4,7 +4,7 @@ export function getBytesUnit(bytes: number): string {
if (isNaN(bytes) || !isFinite(bytes)) { if (isNaN(bytes) || !isFinite(bytes)) {
return 'B'; return 'B';
} }
let unitIndex = 0; let unitIndex = 0;
while (unitIndex < byteUnits.length && bytes > 1024) { while (unitIndex < byteUnits.length && bytes > 1024) {
unitIndex++; unitIndex++;
@ -18,7 +18,7 @@ export function formatBytes(bytes: number, toUnit: string, skipUnit = false): st
if (isNaN(bytes) || !isFinite(bytes)) { if (isNaN(bytes) || !isFinite(bytes)) {
return `${bytes}`; return `${bytes}`;
} }
let unitIndex = 0; let unitIndex = 0;
while (unitIndex < byteUnits.length && (toUnit && byteUnits[unitIndex] !== toUnit || (!toUnit && bytes > 1024))) { while (unitIndex < byteUnits.length && (toUnit && byteUnits[unitIndex] !== toUnit || (!toUnit && bytes > 1024))) {
unitIndex++; unitIndex++;

View file

@ -49,7 +49,7 @@ export function isPoint(pointHex: string): boolean {
} }
// Function modified slightly from noble-curves // Function modified slightly from noble-curves
// Now we know that pointHex is a 33 or 65 byte hex string. // Now we know that pointHex is a 33 or 65 byte hex string.
const isCompressed = pointHex.length === 66; const isCompressed = pointHex.length === 66;

View file

@ -1,5 +1,5 @@
// Integration test setup - uses real implementations, not mocks // Integration test setup - uses real implementations, not mocks
// //
// Note: We don't mock ./mempool-config.json here because: // Note: We don't mock ./mempool-config.json here because:
// 1. The MEMPOOL_CONFIG_FILE env var points to mempool-config.test.json // 1. The MEMPOOL_CONFIG_FILE env var points to mempool-config.test.json
// 2. config.ts will load that file via require() if env var is set // 2. config.ts will load that file via require() if env var is set