From 63939ddbe41f6b1954e267c7161ad825a72dad79 Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Tue, 18 Jan 2022 22:25:38 +0100 Subject: [PATCH 01/47] outputs of genesis coinbase are always unspent --- backend/src/api/bitcoin/bitcoin-api.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index 79505d5c3..c23e86ee0 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -106,10 +106,16 @@ class BitcoinApi implements AbstractBitcoinApi { const outSpends: IEsploraApi.Outspend[] = []; const tx = await this.$getRawTransaction(txId, true, false); for (let i = 0; i < tx.vout.length; i++) { - const txOut = await this.bitcoindClient.getTxOut(txId, i); - outSpends.push({ - spent: txOut === null, - }); + if (tx.status && tx.status.block_height == 0) { + outSpends.push({ + spent: false + }); + } else { + const txOut = await this.bitcoindClient.getTxOut(txId, i); + outSpends.push({ + spent: txOut === null, + }); + } } return outSpends; } From 2848f56c2b975c39533294a6f7309224e4c3e1c2 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 19 Jan 2022 18:50:52 +0900 Subject: [PATCH 02/47] Import mining pools into the database - Increment db schema to 3 --- backend/src/api/database-migration.ts | 20 ++++- backend/src/api/pools-parser.ts | 118 ++++++++++++++++++++++++++ backend/src/index.ts | 2 + 3 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 backend/src/api/pools-parser.ts diff --git a/backend/src/api/database-migration.ts b/backend/src/api/database-migration.ts index 2ac97636e..9fdd4d210 100644 --- a/backend/src/api/database-migration.ts +++ b/backend/src/api/database-migration.ts @@ -3,10 +3,10 @@ import config from '../config'; import { DB } from '../database'; import logger from '../logger'; -const sleep = (ms: number) => new Promise( res => setTimeout(res, ms)); +const sleep = (ms: number) => new Promise(res => setTimeout(res, ms)); class DatabaseMigration { - private static currentVersion = 2; + private static currentVersion = 3; private queryTimeout = 120000; private statisticsAddedIndexed = false; @@ -83,6 +83,9 @@ class DatabaseMigration { if (databaseSchemaVersion < 2 && this.statisticsAddedIndexed === false) { await this.$executeQuery(connection, `CREATE INDEX added ON statistics (added);`); } + if (databaseSchemaVersion < 3) { + await this.$executeQuery(connection, this.getCreatePoolsTableQuery(), await this.$checkIfTableExists('pools')); + } connection.release(); } catch (e) { connection.release(); @@ -335,6 +338,17 @@ class DatabaseMigration { final_tx int(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`; } + + private getCreatePoolsTableQuery(): string { + return `CREATE TABLE IF NOT EXISTS pools ( + id int(11) NOT NULL AUTO_INCREMENT, + name varchar(50) NOT NULL, + link varchar(255) NOT NULL, + addresses text NOT NULL, + regexes text NOT NULL, + PRIMARY KEY (id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`; + } } -export default new DatabaseMigration(); +export default new DatabaseMigration(); \ No newline at end of file diff --git a/backend/src/api/pools-parser.ts b/backend/src/api/pools-parser.ts new file mode 100644 index 000000000..c17336288 --- /dev/null +++ b/backend/src/api/pools-parser.ts @@ -0,0 +1,118 @@ +import {readFileSync} from 'fs'; +import { DB } from '../database'; +import logger from '../logger'; + +interface Pool { + name: string, + link: string, + regexes: string[], + addresses: string[], +} + +class PoolsParser { + /** + * Parse the pools.json file, consolidate the data and dump it into the database + */ + public async migratePoolsJson() { + logger.info('Importing pools.json to the database'); + let connection = await DB.pool.getConnection(); + + // Check if the pools table does not have data already, for now we do not support updating it + // but that will come in a later version + let [rows] = await connection.query({ sql: 'SELECT count(id) as count from pools;', timeout: 120000 }); + if (rows[0].count !== 0) { + logger.info('Pools table already contain data, updating it is not yet supported, skipping.'); + connection.release(); + return; + } + + logger.info('Open ../frontend/cypress/fixtures/pools.json'); + const fileContent: string = readFileSync('../frontend/cypress/fixtures/pools.json','utf8'); + const poolsJson: object = JSON.parse(fileContent); + + // First we save every entries without paying attention to pool duplication + let poolsDuplicated: Pool[] = []; + + logger.info('Parse coinbase_tags'); + const coinbaseTags = Object.entries(poolsJson['coinbase_tags']); + for (let i = 0; i < coinbaseTags.length; ++i) { + poolsDuplicated.push({ + 'name': (coinbaseTags[i][1]).name, + 'link': (coinbaseTags[i][1]).link, + 'regexes': [coinbaseTags[i][0]], + 'addresses': [], + }); + } + logger.info('Parse payout_addresses'); + const addressesTags = Object.entries(poolsJson['payout_addresses']); + for (let i = 0; i < addressesTags.length; ++i) { + poolsDuplicated.push({ + 'name': (addressesTags[i][1]).name, + 'link': (addressesTags[i][1]).link, + 'regexes': [], + 'addresses': [addressesTags[i][0]], + }); + } + + // Then, we find unique mining pool names + logger.info('Identify unique mining pools'); + let poolNames : string[] = []; + for (let i = 0; i < poolsDuplicated.length; ++i) { + if (poolNames.indexOf(poolsDuplicated[i].name) === -1) { + poolNames.push(poolsDuplicated[i].name); + } + } + logger.info(`Found ${poolNames.length} unique mining pools`); + + // Finally, we generate the final consolidated pools data + let finalPoolData: Pool[] = []; + for (let i = 0; i < poolNames.length; ++i) { + let allAddresses: string[] = []; + let allRegexes: string[] = []; + let match = poolsDuplicated.filter((pool: Pool) => pool.name === poolNames[i]); + + for (let y = 0; y < match.length; ++y) { + allAddresses = allAddresses.concat(match[y].addresses); + allRegexes = allRegexes.concat(match[y].regexes); + } + + finalPoolData.push({ + 'name': poolNames[i].replace("'", "''"), + 'link': match[0].link, + 'regexes': allRegexes, + 'addresses': allAddresses, + }) + } + + // Manually add the 'unknown pool' + finalPoolData.push({ + 'name': 'Unknown', + 'link': 'https://learnmeabitcoin.com/technical/coinbase-transaction', + regexes: [], + addresses: [], + }) + + // Dump everything into the database + logger.info(`Insert mining pool info into the database`); + let query: string = 'INSERT INTO pools(name, link, regexes, addresses) VALUES '; + for (let i = 0; i < finalPoolData.length; ++i) { + query += `('${finalPoolData[i].name}', '${finalPoolData[i].link}', + '${JSON.stringify(finalPoolData[i].regexes)}', '${JSON.stringify(finalPoolData[i].addresses)}'),`; + } + query = query.slice(0, -1) + ';'; + + try { + await connection.query({ sql: 'DELETE FROM pools;', timeout: 120000 }); // We clear the table before insertion + await connection.query({ sql: query, timeout: 120000 }); + connection.release(); + logger.info('Import completed'); + } catch (e) { + connection.release(); + logger.info(`Unable to import pools in the database!`); + throw e; + } + } + +} + +export default new PoolsParser(); \ No newline at end of file diff --git a/backend/src/index.ts b/backend/src/index.ts index f6615d1c8..9e4dcee35 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -22,6 +22,7 @@ import loadingIndicators from './api/loading-indicators'; import mempool from './api/mempool'; import elementsParser from './api/liquid/elements-parser'; import databaseMigration from './api/database-migration'; +import poolsParser from './api/pools-parser'; import syncAssets from './sync-assets'; import icons from './api/liquid/icons'; import { Common } from './api/common'; @@ -88,6 +89,7 @@ class Server { await checkDbConnection(); try { await databaseMigration.$initializeOrMigrateDatabase(); + await poolsParser.migratePoolsJson(); } catch (e) { throw new Error(e instanceof Error ? e.message : 'Error'); } From 979c52d3c413532411e0ca713333f12592e21c69 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 20 Jan 2022 13:53:08 +0900 Subject: [PATCH 03/47] Add pools.json to EXTERNAL_ASSETS - Now supports updating the table --- backend/mempool-config.sample.json | 4 +- backend/src/api/pools-parser.ts | 94 +++++++++++++++++++----------- 2 files changed, 63 insertions(+), 35 deletions(-) diff --git a/backend/mempool-config.sample.json b/backend/mempool-config.sample.json index ea656c1de..1b55f38f4 100644 --- a/backend/mempool-config.sample.json +++ b/backend/mempool-config.sample.json @@ -14,7 +14,9 @@ "MEMPOOL_BLOCKS_AMOUNT": 8, "PRICE_FEED_UPDATE_INTERVAL": 3600, "USE_SECOND_NODE_FOR_MINFEE": false, - "EXTERNAL_ASSETS": [] + "EXTERNAL_ASSETS": [ + "https://mempool.space/resources/pools.json" + ] }, "CORE_RPC": { "HOST": "127.0.0.1", diff --git a/backend/src/api/pools-parser.ts b/backend/src/api/pools-parser.ts index c17336288..5e6a38e49 100644 --- a/backend/src/api/pools-parser.ts +++ b/backend/src/api/pools-parser.ts @@ -14,20 +14,14 @@ class PoolsParser { * Parse the pools.json file, consolidate the data and dump it into the database */ public async migratePoolsJson() { + const connection = await DB.pool.getConnection(); logger.info('Importing pools.json to the database'); - let connection = await DB.pool.getConnection(); - // Check if the pools table does not have data already, for now we do not support updating it - // but that will come in a later version - let [rows] = await connection.query({ sql: 'SELECT count(id) as count from pools;', timeout: 120000 }); - if (rows[0].count !== 0) { - logger.info('Pools table already contain data, updating it is not yet supported, skipping.'); - connection.release(); - return; - } + // Get existing pools from the db + const [existingPools] = await connection.query({ sql: 'SELECT * FROM pools;', timeout: 120000 }); // We clear the table before insertion - logger.info('Open ../frontend/cypress/fixtures/pools.json'); - const fileContent: string = readFileSync('../frontend/cypress/fixtures/pools.json','utf8'); + logger.info('Open ./pools.json'); + const fileContent: string = readFileSync('./pools.json','utf8'); const poolsJson: object = JSON.parse(fileContent); // First we save every entries without paying attention to pool duplication @@ -65,7 +59,8 @@ class PoolsParser { logger.info(`Found ${poolNames.length} unique mining pools`); // Finally, we generate the final consolidated pools data - let finalPoolData: Pool[] = []; + let finalPoolDataAdd: Pool[] = []; + let finalPoolDataUpdate: Pool[] = []; for (let i = 0; i < poolNames.length; ++i) { let allAddresses: string[] = []; let allRegexes: string[] = []; @@ -76,34 +71,65 @@ class PoolsParser { allRegexes = allRegexes.concat(match[y].regexes); } - finalPoolData.push({ - 'name': poolNames[i].replace("'", "''"), - 'link': match[0].link, - 'regexes': allRegexes, - 'addresses': allAddresses, - }) + const finalPoolName = poolNames[i].replace("'", "''"); // To support single quote in names when doing db queries + + if (existingPools.find((pool) => { return pool.name === poolNames[i]}) !== undefined) { + logger.debug(`Update '${finalPoolName} mining pool`); + finalPoolDataUpdate.push({ + 'name': finalPoolName, + 'link': match[0].link, + 'regexes': allRegexes, + 'addresses': allAddresses, + }) + } else { + logger.debug(`Add '${finalPoolName} mining pool`); + finalPoolDataAdd.push({ + 'name': finalPoolName, + 'link': match[0].link, + 'regexes': allRegexes, + 'addresses': allAddresses, + }) + } } // Manually add the 'unknown pool' - finalPoolData.push({ - 'name': 'Unknown', - 'link': 'https://learnmeabitcoin.com/technical/coinbase-transaction', - regexes: [], - addresses: [], - }) - - // Dump everything into the database - logger.info(`Insert mining pool info into the database`); - let query: string = 'INSERT INTO pools(name, link, regexes, addresses) VALUES '; - for (let i = 0; i < finalPoolData.length; ++i) { - query += `('${finalPoolData[i].name}', '${finalPoolData[i].link}', - '${JSON.stringify(finalPoolData[i].regexes)}', '${JSON.stringify(finalPoolData[i].addresses)}'),`; + if (existingPools.find((pool) => { return pool.name === "Uknown"}) !== undefined) { + finalPoolDataAdd.push({ + 'name': 'Unknown', + 'link': 'https://learnmeabitcoin.com/technical/coinbase-transaction', + regexes: [], + addresses: [], + }) + } + + logger.info(`Update pools table now`); + + // Add new mining pools into the database + let queryAdd: string = 'INSERT INTO pools(name, link, regexes, addresses) VALUES '; + for (let i = 0; i < finalPoolDataAdd.length; ++i) { + queryAdd += `('${finalPoolDataAdd[i].name}', '${finalPoolDataAdd[i].link}', + '${JSON.stringify(finalPoolDataAdd[i].regexes)}', '${JSON.stringify(finalPoolDataAdd[i].addresses)}'),`; + } + queryAdd = queryAdd.slice(0, -1) + ';'; + + // Add new mining pools into the database + let updateQueries: string[] = []; + for (let i = 0; i < finalPoolDataUpdate.length; ++i) { + updateQueries.push(` + UPDATE pools + SET name='${finalPoolDataUpdate[i].name}', link='${finalPoolDataUpdate[i].link}', + regexes='${JSON.stringify(finalPoolDataUpdate[i].regexes)}', addresses='${JSON.stringify(finalPoolDataUpdate[i].addresses)}' + WHERE name='${finalPoolDataUpdate[i].name}' + ;`); } - query = query.slice(0, -1) + ';'; try { - await connection.query({ sql: 'DELETE FROM pools;', timeout: 120000 }); // We clear the table before insertion - await connection.query({ sql: query, timeout: 120000 }); + if (finalPoolDataAdd.length > 0) { + await connection.query({ sql: queryAdd, timeout: 120000 }); + } + updateQueries.forEach(async query => { + await connection.query({ sql: query, timeout: 120000 }); + }); connection.release(); logger.info('Import completed'); } catch (e) { From 1210643e8ec9aaaa6952f000bdf9c82bdc5dc0e1 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 20 Jan 2022 16:34:14 +0900 Subject: [PATCH 04/47] Fix linter issues and typo --- backend/src/api/pools-parser.ts | 42 ++++++++++++++++----------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/backend/src/api/pools-parser.ts b/backend/src/api/pools-parser.ts index 5e6a38e49..da048f2d3 100644 --- a/backend/src/api/pools-parser.ts +++ b/backend/src/api/pools-parser.ts @@ -3,10 +3,10 @@ import { DB } from '../database'; import logger from '../logger'; interface Pool { - name: string, - link: string, - regexes: string[], - addresses: string[], + name: string; + link: string; + regexes: string[]; + addresses: string[]; } class PoolsParser { @@ -18,14 +18,14 @@ class PoolsParser { logger.info('Importing pools.json to the database'); // Get existing pools from the db - const [existingPools] = await connection.query({ sql: 'SELECT * FROM pools;', timeout: 120000 }); // We clear the table before insertion + const [existingPools] = await connection.query({ sql: 'SELECT * FROM pools;', timeout: 120000 }); logger.info('Open ./pools.json'); - const fileContent: string = readFileSync('./pools.json','utf8'); + const fileContent: string = readFileSync('./pools.json', 'utf8'); const poolsJson: object = JSON.parse(fileContent); // First we save every entries without paying attention to pool duplication - let poolsDuplicated: Pool[] = []; + const poolsDuplicated: Pool[] = []; logger.info('Parse coinbase_tags'); const coinbaseTags = Object.entries(poolsJson['coinbase_tags']); @@ -50,7 +50,7 @@ class PoolsParser { // Then, we find unique mining pool names logger.info('Identify unique mining pools'); - let poolNames : string[] = []; + const poolNames: string[] = []; for (let i = 0; i < poolsDuplicated.length; ++i) { if (poolNames.indexOf(poolsDuplicated[i].name) === -1) { poolNames.push(poolsDuplicated[i].name); @@ -59,47 +59,47 @@ class PoolsParser { logger.info(`Found ${poolNames.length} unique mining pools`); // Finally, we generate the final consolidated pools data - let finalPoolDataAdd: Pool[] = []; - let finalPoolDataUpdate: Pool[] = []; + const finalPoolDataAdd: Pool[] = []; + const finalPoolDataUpdate: Pool[] = []; for (let i = 0; i < poolNames.length; ++i) { let allAddresses: string[] = []; let allRegexes: string[] = []; - let match = poolsDuplicated.filter((pool: Pool) => pool.name === poolNames[i]); + const match = poolsDuplicated.filter((pool: Pool) => pool.name === poolNames[i]); for (let y = 0; y < match.length; ++y) { allAddresses = allAddresses.concat(match[y].addresses); allRegexes = allRegexes.concat(match[y].regexes); } - const finalPoolName = poolNames[i].replace("'", "''"); // To support single quote in names when doing db queries + const finalPoolName = poolNames[i].replace(`'`, `''`); // To support single quote in names when doing db queries - if (existingPools.find((pool) => { return pool.name === poolNames[i]}) !== undefined) { - logger.debug(`Update '${finalPoolName} mining pool`); + if (existingPools.find((pool) => pool.name === poolNames[i]) !== undefined) { + logger.debug(`Update '${finalPoolName}' mining pool`); finalPoolDataUpdate.push({ 'name': finalPoolName, 'link': match[0].link, 'regexes': allRegexes, 'addresses': allAddresses, - }) + }); } else { - logger.debug(`Add '${finalPoolName} mining pool`); + logger.debug(`Add '${finalPoolName}' mining pool`); finalPoolDataAdd.push({ 'name': finalPoolName, 'link': match[0].link, 'regexes': allRegexes, 'addresses': allAddresses, - }) + }); } } // Manually add the 'unknown pool' - if (existingPools.find((pool) => { return pool.name === "Uknown"}) !== undefined) { + if (existingPools.find((pool) => pool.name === 'Unknown') !== undefined) { finalPoolDataAdd.push({ 'name': 'Unknown', 'link': 'https://learnmeabitcoin.com/technical/coinbase-transaction', regexes: [], addresses: [], - }) + }); } logger.info(`Update pools table now`); @@ -113,7 +113,7 @@ class PoolsParser { queryAdd = queryAdd.slice(0, -1) + ';'; // Add new mining pools into the database - let updateQueries: string[] = []; + const updateQueries: string[] = []; for (let i = 0; i < finalPoolDataUpdate.length; ++i) { updateQueries.push(` UPDATE pools @@ -141,4 +141,4 @@ class PoolsParser { } -export default new PoolsParser(); \ No newline at end of file +export default new PoolsParser(); From 8d1cc40459679d10f0ce12b44a3452fc2208d827 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 20 Jan 2022 16:56:25 +0900 Subject: [PATCH 05/47] Fix add 'Unknown' pool logic --- backend/src/api/pools-parser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/api/pools-parser.ts b/backend/src/api/pools-parser.ts index da048f2d3..9bfcd3366 100644 --- a/backend/src/api/pools-parser.ts +++ b/backend/src/api/pools-parser.ts @@ -93,7 +93,7 @@ class PoolsParser { } // Manually add the 'unknown pool' - if (existingPools.find((pool) => pool.name === 'Unknown') !== undefined) { + if (existingPools.find((pool) => pool.name === 'Unknown') === undefined) { finalPoolDataAdd.push({ 'name': 'Unknown', 'link': 'https://learnmeabitcoin.com/technical/coinbase-transaction', From 19a564062bed4ced0a5e407a744218941ca5c77d Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 20 Jan 2022 22:59:10 +0900 Subject: [PATCH 06/47] Add pools.json file in default config.ts - Handle file exception - Only import pools for MAINNET --- backend/src/api/pools-parser.ts | 82 ++++++++++++++++++++------------- backend/src/config.ts | 4 +- 2 files changed, 54 insertions(+), 32 deletions(-) diff --git a/backend/src/api/pools-parser.ts b/backend/src/api/pools-parser.ts index 9bfcd3366..bb7779089 100644 --- a/backend/src/api/pools-parser.ts +++ b/backend/src/api/pools-parser.ts @@ -1,6 +1,7 @@ -import {readFileSync} from 'fs'; +import { readFileSync } from 'fs'; import { DB } from '../database'; import logger from '../logger'; +import config from '../config'; interface Pool { name: string; @@ -14,20 +15,26 @@ class PoolsParser { * Parse the pools.json file, consolidate the data and dump it into the database */ public async migratePoolsJson() { - const connection = await DB.pool.getConnection(); - logger.info('Importing pools.json to the database'); + if (config.MEMPOOL.NETWORK !== 'mainnet') { + return; + } - // Get existing pools from the db - const [existingPools] = await connection.query({ sql: 'SELECT * FROM pools;', timeout: 120000 }); + logger.debug('Importing pools.json to the database, open ./pools.json'); - logger.info('Open ./pools.json'); - const fileContent: string = readFileSync('./pools.json', 'utf8'); - const poolsJson: object = JSON.parse(fileContent); + let poolsJson: object = {}; + try { + const fileContent: string = readFileSync('./pools.json', 'utf8'); + poolsJson = JSON.parse(fileContent); + } catch (e) { + logger.err('Unable to open ./pools.json, does the file exist?'); + await this.insertUnknownPool(); + return; + } // First we save every entries without paying attention to pool duplication const poolsDuplicated: Pool[] = []; - logger.info('Parse coinbase_tags'); + logger.debug('Parse coinbase_tags'); const coinbaseTags = Object.entries(poolsJson['coinbase_tags']); for (let i = 0; i < coinbaseTags.length; ++i) { poolsDuplicated.push({ @@ -37,7 +44,7 @@ class PoolsParser { 'addresses': [], }); } - logger.info('Parse payout_addresses'); + logger.debug('Parse payout_addresses'); const addressesTags = Object.entries(poolsJson['payout_addresses']); for (let i = 0; i < addressesTags.length; ++i) { poolsDuplicated.push({ @@ -49,14 +56,18 @@ class PoolsParser { } // Then, we find unique mining pool names - logger.info('Identify unique mining pools'); + logger.debug('Identify unique mining pools'); const poolNames: string[] = []; for (let i = 0; i < poolsDuplicated.length; ++i) { if (poolNames.indexOf(poolsDuplicated[i].name) === -1) { poolNames.push(poolsDuplicated[i].name); } } - logger.info(`Found ${poolNames.length} unique mining pools`); + logger.debug(`Found ${poolNames.length} unique mining pools`); + + // Get existing pools from the db + const connection = await DB.pool.getConnection(); + const [existingPools] = await connection.query({ sql: 'SELECT * FROM pools;', timeout: 120000 }); // Finally, we generate the final consolidated pools data const finalPoolDataAdd: Pool[] = []; @@ -92,23 +103,13 @@ class PoolsParser { } } - // Manually add the 'unknown pool' - if (existingPools.find((pool) => pool.name === 'Unknown') === undefined) { - finalPoolDataAdd.push({ - 'name': 'Unknown', - 'link': 'https://learnmeabitcoin.com/technical/coinbase-transaction', - regexes: [], - addresses: [], - }); - } - - logger.info(`Update pools table now`); + logger.debug(`Update pools table now`); // Add new mining pools into the database let queryAdd: string = 'INSERT INTO pools(name, link, regexes, addresses) VALUES '; for (let i = 0; i < finalPoolDataAdd.length; ++i) { queryAdd += `('${finalPoolDataAdd[i].name}', '${finalPoolDataAdd[i].link}', - '${JSON.stringify(finalPoolDataAdd[i].regexes)}', '${JSON.stringify(finalPoolDataAdd[i].addresses)}'),`; + '${JSON.stringify(finalPoolDataAdd[i].regexes)}', '${JSON.stringify(finalPoolDataAdd[i].addresses)}'),`; } queryAdd = queryAdd.slice(0, -1) + ';'; @@ -116,11 +117,11 @@ class PoolsParser { const updateQueries: string[] = []; for (let i = 0; i < finalPoolDataUpdate.length; ++i) { updateQueries.push(` - UPDATE pools - SET name='${finalPoolDataUpdate[i].name}', link='${finalPoolDataUpdate[i].link}', - regexes='${JSON.stringify(finalPoolDataUpdate[i].regexes)}', addresses='${JSON.stringify(finalPoolDataUpdate[i].addresses)}' - WHERE name='${finalPoolDataUpdate[i].name}' - ;`); + UPDATE pools + SET name='${finalPoolDataUpdate[i].name}', link='${finalPoolDataUpdate[i].link}', + regexes='${JSON.stringify(finalPoolDataUpdate[i].regexes)}', addresses='${JSON.stringify(finalPoolDataUpdate[i].addresses)}' + WHERE name='${finalPoolDataUpdate[i].name}' + ;`); } try { @@ -130,15 +131,34 @@ class PoolsParser { updateQueries.forEach(async query => { await connection.query({ sql: query, timeout: 120000 }); }); + await this.insertUnknownPool(); connection.release(); - logger.info('Import completed'); + logger.info('Mining pools.json import completed'); } catch (e) { connection.release(); - logger.info(`Unable to import pools in the database!`); + logger.err(`Unable to import pools in the database!`); throw e; } } + /** + * Manually add the 'unknown pool' + */ + private async insertUnknownPool() { + const connection = await DB.pool.getConnection(); + try { + const [rows]: any[] = await connection.query({ sql: 'SELECT name from pools where name="Unknown"', timeout: 120000 }); + if (rows.length === 0) { + logger.debug('Manually inserting "Unknown" mining pool into the databse'); + await connection.query({ + sql: `INSERT INTO pools(name, link, regexes, addresses) + VALUES("Unknown", "https://learnmeabitcoin.com/technical/coinbase-transaction", "[]", "[]"); + `}); + } + } catch (e) { + logger.err('Unable to insert "Unknown" mining pool'); + } + } } export default new PoolsParser(); diff --git a/backend/src/config.ts b/backend/src/config.ts index 4c2888834..3cc928327 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -79,7 +79,9 @@ const defaults: IConfig = { 'MEMPOOL_BLOCKS_AMOUNT': 8, 'PRICE_FEED_UPDATE_INTERVAL': 3600, 'USE_SECOND_NODE_FOR_MINFEE': false, - 'EXTERNAL_ASSETS': [], + 'EXTERNAL_ASSETS': [ + 'https://mempool.space/resources/pools.json' + ] }, 'ESPLORA': { 'REST_API_URL': 'http://127.0.0.1:3000', From a1a2e9363fc7137980ba37a2e80889539a50cd4a Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 20 Jan 2022 23:07:20 +0900 Subject: [PATCH 07/47] Make sure to release all db connections --- backend/src/api/pools-parser.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/backend/src/api/pools-parser.ts b/backend/src/api/pools-parser.ts index bb7779089..2b0901551 100644 --- a/backend/src/api/pools-parser.ts +++ b/backend/src/api/pools-parser.ts @@ -67,7 +67,14 @@ class PoolsParser { // Get existing pools from the db const connection = await DB.pool.getConnection(); - const [existingPools] = await connection.query({ sql: 'SELECT * FROM pools;', timeout: 120000 }); + let existingPools: any[] = []; + try { + existingPools = await connection.query({ sql: 'SELECT * FROM pools;', timeout: 120000 }); + } catch (e) { + logger.err('Unable to get existing pools from the database, skipping pools.json import'); + connection.release(); + return; + } // Finally, we generate the final consolidated pools data const finalPoolDataAdd: Pool[] = []; @@ -117,11 +124,11 @@ class PoolsParser { const updateQueries: string[] = []; for (let i = 0; i < finalPoolDataUpdate.length; ++i) { updateQueries.push(` - UPDATE pools - SET name='${finalPoolDataUpdate[i].name}', link='${finalPoolDataUpdate[i].link}', - regexes='${JSON.stringify(finalPoolDataUpdate[i].regexes)}', addresses='${JSON.stringify(finalPoolDataUpdate[i].addresses)}' - WHERE name='${finalPoolDataUpdate[i].name}' - ;`); + UPDATE pools + SET name='${finalPoolDataUpdate[i].name}', link='${finalPoolDataUpdate[i].link}', + regexes='${JSON.stringify(finalPoolDataUpdate[i].regexes)}', addresses='${JSON.stringify(finalPoolDataUpdate[i].addresses)}' + WHERE name='${finalPoolDataUpdate[i].name}' + ;`); } try { @@ -158,6 +165,8 @@ class PoolsParser { } catch (e) { logger.err('Unable to insert "Unknown" mining pool'); } + + connection.release(); } } From 87175869dd741ca366eddce5bdcc36928157e552 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Thu, 20 Jan 2022 23:31:32 +0900 Subject: [PATCH 08/47] Fix typescript miss use --- backend/src/api/pools-parser.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/api/pools-parser.ts b/backend/src/api/pools-parser.ts index 2b0901551..e3f5f0b89 100644 --- a/backend/src/api/pools-parser.ts +++ b/backend/src/api/pools-parser.ts @@ -67,9 +67,9 @@ class PoolsParser { // Get existing pools from the db const connection = await DB.pool.getConnection(); - let existingPools: any[] = []; + let existingPools; try { - existingPools = await connection.query({ sql: 'SELECT * FROM pools;', timeout: 120000 }); + [existingPools] = await connection.query({ sql: 'SELECT * FROM pools;', timeout: 120000 }); } catch (e) { logger.err('Unable to get existing pools from the database, skipping pools.json import'); connection.release(); From a8c04624f08f4550c2ae3b4e2c909936845ad7e6 Mon Sep 17 00:00:00 2001 From: softsimon Date: Fri, 21 Jan 2022 01:32:19 +0400 Subject: [PATCH 09/47] Fixing liqud asset precision fixes #1166 --- .../transactions-list/transactions-list.component.html | 2 +- .../transactions-list/transactions-list.component.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.html b/frontend/src/app/components/transactions-list/transactions-list.component.html index 36aed7fe6..680b6be53 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.html +++ b/frontend/src/app/components/transactions-list/transactions-list.component.html @@ -270,7 +270,7 @@ - {{ item.value / 100000000 | number: '1.0-' + assetsMinimal[item.asset][3] }} {{ assetsMinimal[item.asset][1] }} + {{ item.value / pow(10, assetsMinimal[item.asset][3]) | number: '1.' + assetsMinimal[item.asset][3] + '-' + assetsMinimal[item.asset][3] }} {{ assetsMinimal[item.asset][1] }}
{{ assetsMinimal[item.asset][0] }}
diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.ts b/frontend/src/app/components/transactions-list/transactions-list.component.ts index ecddf4436..e1fb7d2e6 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.ts +++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts @@ -119,6 +119,10 @@ export class TransactionsListComponent implements OnInit, OnChanges { return '0x' + (str.length % 2 ? '0' : '') + str; } + pow(base: number, exponent: number): number { + return Math.pow(base, exponent); + } + toggleDetails() { this.displayDetails = !this.displayDetails; this.ref.markForCheck(); From 1322298a06a0c9f0182ac7ea0317dfe16573858f Mon Sep 17 00:00:00 2001 From: nymkappa Date: Mon, 24 Jan 2022 14:34:03 +0900 Subject: [PATCH 10/47] Make sure to wait for all mining pools queries before continuing --- backend/src/api/pools-parser.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/api/pools-parser.ts b/backend/src/api/pools-parser.ts index e3f5f0b89..b81bf9d15 100644 --- a/backend/src/api/pools-parser.ts +++ b/backend/src/api/pools-parser.ts @@ -135,9 +135,9 @@ class PoolsParser { if (finalPoolDataAdd.length > 0) { await connection.query({ sql: queryAdd, timeout: 120000 }); } - updateQueries.forEach(async query => { + for (const query of updateQueries) { await connection.query({ sql: query, timeout: 120000 }); - }); + } await this.insertUnknownPool(); connection.release(); logger.info('Mining pools.json import completed'); From 703b4cc92a3ec728a6767044254b33aac05ab622 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 25 Jan 2022 16:45:52 +0900 Subject: [PATCH 11/47] Remove useless autocommit=0 in db migration script --- backend/src/api/database-migration.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/api/database-migration.ts b/backend/src/api/database-migration.ts index 9fdd4d210..a375b7bf4 100644 --- a/backend/src/api/database-migration.ts +++ b/backend/src/api/database-migration.ts @@ -200,7 +200,6 @@ class DatabaseMigration { const connection = await DB.pool.getConnection(); try { await this.$executeQuery(connection, 'START TRANSACTION;'); - await this.$executeQuery(connection, 'SET autocommit = 0;'); for (const query of transactionQueries) { await this.$executeQuery(connection, query); } From 000dfc4d9e315f0d45e7f0b6b7689330e43292e3 Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 25 Jan 2022 09:15:19 +0000 Subject: [PATCH 12/47] Add new Contributor License Agreement policy --- CONTRIBUTING.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..91006c3f6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,49 @@ +# Contributing to The Mempool Open Source Project + +Thank you for contributing to The Mempool Open Source Project managed by Mempool Space K.K. (“Mempool”). + +In order to clarify the intellectual property license granted with Contributions from any person or entity, Mempool must have a statement on file from each Contributor indicating their agreement to the Contributor License Agreement (“Agreement”). This license is for your protection as a Contributor as well as the protection of Mempool and its other contributors and users; it does not change your rights to use your own Contributions for any other purpose. + +When submitting a pull request for the first time, please create a file with a name like `/contributors/{github_username}.txt`, and in the content of that file indicate your agreement to the Contributor License Agreement terms below. An example of what that file should contain can be seen in wiz's agreement file. (This method of CLA "signing" is borrowed from Medium's open source project.) + +# Contributor License Agreement + +Last Updated: January 25, 2022 + +By accepting this Agreement, You agree to the following terms and conditions for Your present and future Contributions submitted to Mempool. Except for the license granted herein to Mempool and recipients of software distributed by Mempool, You reserve all right, title, and interest in and to Your Contributions. + +### 1. Definitions + +“You” (or “Your”) shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with Mempool. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, “control” means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +“Contribution” shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to Mempool for inclusion in, or documentation of, any of the products owned or managed by Mempool (“Work”). For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to Mempool or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, Mempool for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as “Not a Contribution.” + +### 2. Grant of Copyright License + +Subject to the terms and conditions of this Agreement, You hereby grant to Mempool and to recipients of software distributed by Mempool a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. + +### 3. Grant of Patent License + +Subject to the terms and conditions of this Agreement, You hereby grant to Mempool and to recipients of software distributed by Mempool a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. + +### 4. Authority + +You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to Mempool, or that your employer has executed a separate Corporate Contributor License Agreement with Mempool. + +### 5. Originality + +You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware, and which are associated with any part of Your Contributions. + +### 6. Support + +You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +### 7. Third Party Contributions + +Should You wish to submit work that is not Your original creation, You may submit it to Mempool separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as “Submitted on behalf of a third-party: [named here]”. + +### 8. Notifications + +You agree to notify Mempool of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect. + +EOF From 707ae7be01aaa3d3cd2f2471b9b341cd13c404f3 Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 25 Jan 2022 09:19:09 +0000 Subject: [PATCH 13/47] Accept the CLA for @wiz --- contributors/wiz.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 contributors/wiz.txt diff --git a/contributors/wiz.txt b/contributors/wiz.txt new file mode 100644 index 000000000..a006b4e04 --- /dev/null +++ b/contributors/wiz.txt @@ -0,0 +1,3 @@ +I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of January 25, 2022. + +Signed: wiz From bd033541b7cdc769cdbe18b8feb08231180144ef Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 25 Jan 2022 09:19:30 +0000 Subject: [PATCH 14/47] Update copyright years in legal notices --- LICENSE | 2 +- frontend/src/app/components/about/about.component.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 1826f463d..966417847 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ The Mempool Open Source Project -Copyright (c) 2019-2021 The Mempool Open Source Project Developers +Copyright (c) 2019-2022 The Mempool Open Source Project Developers This program is free software; you can redistribute it and/or modify it under the terms of (at your option) either: diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index 847ed152a..5c4732651 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -220,7 +220,7 @@