mirror of
https://github.com/mempool/mempool.git
synced 2024-12-28 17:24:25 +01:00
Merge branch 'master' into desktop-refinements
This commit is contained in:
commit
ab3f80220c
@ -12,7 +12,7 @@
|
|||||||
"BLOCK_WEIGHT_UNITS": 4000000,
|
"BLOCK_WEIGHT_UNITS": 4000000,
|
||||||
"INITIAL_BLOCKS_AMOUNT": 8,
|
"INITIAL_BLOCKS_AMOUNT": 8,
|
||||||
"MEMPOOL_BLOCKS_AMOUNT": 8,
|
"MEMPOOL_BLOCKS_AMOUNT": 8,
|
||||||
"INDEXING_BLOCKS_AMOUNT": 1100,
|
"INDEXING_BLOCKS_AMOUNT": 11000,
|
||||||
"PRICE_FEED_UPDATE_INTERVAL": 600,
|
"PRICE_FEED_UPDATE_INTERVAL": 600,
|
||||||
"USE_SECOND_NODE_FOR_MINFEE": false,
|
"USE_SECOND_NODE_FOR_MINFEE": false,
|
||||||
"EXTERNAL_ASSETS": [
|
"EXTERNAL_ASSETS": [
|
||||||
@ -44,6 +44,7 @@
|
|||||||
"ENABLED": true,
|
"ENABLED": true,
|
||||||
"HOST": "127.0.0.1",
|
"HOST": "127.0.0.1",
|
||||||
"PORT": 3306,
|
"PORT": 3306,
|
||||||
|
"SOCKET": "/var/run/mysql/mysql.sock",
|
||||||
"DATABASE": "mempool",
|
"DATABASE": "mempool",
|
||||||
"USERNAME": "mempool",
|
"USERNAME": "mempool",
|
||||||
"PASSWORD": "mempool"
|
"PASSWORD": "mempool"
|
||||||
|
@ -70,7 +70,7 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this.bitcoindClient.getBlock(hash)
|
return this.bitcoindClient.getBlock(hash)
|
||||||
.then((block: IBitcoinApi.Block) => this.convertBlock(block));
|
.then((block: IBitcoinApi.Block) => BitcoinApi.convertBlock(block));
|
||||||
}
|
}
|
||||||
|
|
||||||
$getAddress(address: string): Promise<IEsploraApi.Address> {
|
$getAddress(address: string): Promise<IEsploraApi.Address> {
|
||||||
@ -186,7 +186,7 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||||||
return esploraTransaction;
|
return esploraTransaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
private convertBlock(block: IBitcoinApi.Block): IEsploraApi.Block {
|
static convertBlock(block: IBitcoinApi.Block): IEsploraApi.Block {
|
||||||
return {
|
return {
|
||||||
id: block.hash,
|
id: block.hash,
|
||||||
height: block.height,
|
height: block.height,
|
||||||
|
@ -11,6 +11,7 @@ import { IEsploraApi } from './bitcoin/esplora-api.interface';
|
|||||||
import poolsRepository from '../repositories/PoolsRepository';
|
import poolsRepository from '../repositories/PoolsRepository';
|
||||||
import blocksRepository from '../repositories/BlocksRepository';
|
import blocksRepository from '../repositories/BlocksRepository';
|
||||||
import loadingIndicators from './loading-indicators';
|
import loadingIndicators from './loading-indicators';
|
||||||
|
import BitcoinApi from './bitcoin/bitcoin-api';
|
||||||
|
|
||||||
class Blocks {
|
class Blocks {
|
||||||
private blocks: BlockExtended[] = [];
|
private blocks: BlockExtended[] = [];
|
||||||
@ -103,19 +104,28 @@ class Blocks {
|
|||||||
* @param transactions
|
* @param transactions
|
||||||
* @returns BlockExtended
|
* @returns BlockExtended
|
||||||
*/
|
*/
|
||||||
private async $getBlockExtended(block: IEsploraApi.Block, transactions: TransactionExtended[]): Promise<BlockExtended> {
|
private async $getBlockExtended(block: IEsploraApi.Block, transactions: TransactionExtended[]): Promise<BlockExtended> {
|
||||||
const blockExtended: BlockExtended = Object.assign({extras: {}}, block);
|
const blockExtended: BlockExtended = Object.assign({ extras: {} }, block);
|
||||||
blockExtended.extras.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
|
blockExtended.extras.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
|
||||||
blockExtended.extras.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
|
blockExtended.extras.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
|
||||||
|
|
||||||
const stats = await bitcoinClient.getBlockStats(block.id);
|
|
||||||
const coinbaseRaw: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true);
|
const coinbaseRaw: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true);
|
||||||
blockExtended.extras.coinbaseRaw = coinbaseRaw.hex;
|
blockExtended.extras.coinbaseRaw = coinbaseRaw.hex;
|
||||||
blockExtended.extras.medianFee = stats.feerate_percentiles[2]; // 50th percentiles
|
|
||||||
blockExtended.extras.feeRange = [stats.minfeerate, stats.feerate_percentiles, stats.maxfeerate].flat();
|
if (block.height === 0) {
|
||||||
blockExtended.extras.totalFees = stats.totalfee;
|
blockExtended.extras.medianFee = 0; // 50th percentiles
|
||||||
blockExtended.extras.avgFee = stats.avgfee;
|
blockExtended.extras.feeRange = [0, 0, 0, 0, 0, 0, 0];
|
||||||
blockExtended.extras.avgFeeRate = stats.avgfeerate;
|
blockExtended.extras.totalFees = 0;
|
||||||
|
blockExtended.extras.avgFee = 0;
|
||||||
|
blockExtended.extras.avgFeeRate = 0;
|
||||||
|
} else {
|
||||||
|
const stats = await bitcoinClient.getBlockStats(block.id);
|
||||||
|
blockExtended.extras.medianFee = stats.feerate_percentiles[2]; // 50th percentiles
|
||||||
|
blockExtended.extras.feeRange = [stats.minfeerate, stats.feerate_percentiles, stats.maxfeerate].flat();
|
||||||
|
blockExtended.extras.totalFees = stats.totalfee;
|
||||||
|
blockExtended.extras.avgFee = stats.avgfee;
|
||||||
|
blockExtended.extras.avgFeeRate = stats.avgfeerate;
|
||||||
|
}
|
||||||
|
|
||||||
if (Common.indexingEnabled()) {
|
if (Common.indexingEnabled()) {
|
||||||
let pool: PoolTag;
|
let pool: PoolTag;
|
||||||
@ -230,7 +240,7 @@ class Blocks {
|
|||||||
indexedThisRun = 0;
|
indexedThisRun = 0;
|
||||||
}
|
}
|
||||||
const blockHash = await bitcoinApi.$getBlockHash(blockHeight);
|
const blockHash = await bitcoinApi.$getBlockHash(blockHeight);
|
||||||
const block = await bitcoinApi.$getBlock(blockHash);
|
const block = BitcoinApi.convertBlock(await bitcoinClient.getBlock(blockHash));
|
||||||
const transactions = await this.$getTransactionsExtended(blockHash, block.height, true, true);
|
const transactions = await this.$getTransactionsExtended(blockHash, block.height, true, true);
|
||||||
const blockExtended = await this.$getBlockExtended(block, transactions);
|
const blockExtended = await this.$getBlockExtended(block, transactions);
|
||||||
await blocksRepository.$saveBlockInDatabase(blockExtended);
|
await blocksRepository.$saveBlockInDatabase(blockExtended);
|
||||||
@ -267,7 +277,7 @@ class Blocks {
|
|||||||
if (blockchainInfo.blocks === blockchainInfo.headers) {
|
if (blockchainInfo.blocks === blockchainInfo.headers) {
|
||||||
const heightDiff = blockHeightTip % 2016;
|
const heightDiff = blockHeightTip % 2016;
|
||||||
const blockHash = await bitcoinApi.$getBlockHash(blockHeightTip - heightDiff);
|
const blockHash = await bitcoinApi.$getBlockHash(blockHeightTip - heightDiff);
|
||||||
const block = await bitcoinApi.$getBlock(blockHash);
|
const block = BitcoinApi.convertBlock(await bitcoinClient.getBlock(blockHash));
|
||||||
this.lastDifficultyAdjustmentTime = block.timestamp;
|
this.lastDifficultyAdjustmentTime = block.timestamp;
|
||||||
this.currentDifficulty = block.difficulty;
|
this.currentDifficulty = block.difficulty;
|
||||||
|
|
||||||
@ -291,7 +301,7 @@ class Blocks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const blockHash = await bitcoinApi.$getBlockHash(this.currentBlockHeight);
|
const blockHash = await bitcoinApi.$getBlockHash(this.currentBlockHeight);
|
||||||
const block = await bitcoinApi.$getBlock(blockHash);
|
const block = BitcoinApi.convertBlock(await bitcoinClient.getBlock(blockHash));
|
||||||
const txIds: string[] = await bitcoinApi.$getTxIdsForBlock(blockHash);
|
const txIds: string[] = await bitcoinApi.$getTxIdsForBlock(blockHash);
|
||||||
const transactions = await this.$getTransactionsExtended(blockHash, block.height, false);
|
const transactions = await this.$getTransactionsExtended(blockHash, block.height, false);
|
||||||
const blockExtended: BlockExtended = await this.$getBlockExtended(block, transactions);
|
const blockExtended: BlockExtended = await this.$getBlockExtended(block, transactions);
|
||||||
@ -323,23 +333,26 @@ class Blocks {
|
|||||||
/**
|
/**
|
||||||
* Index a block if it's missing from the database. Returns the block after indexing
|
* Index a block if it's missing from the database. Returns the block after indexing
|
||||||
*/
|
*/
|
||||||
public async $indexBlock(height: number): Promise<BlockExtended> {
|
public async $indexBlock(height: number): Promise<BlockExtended> {
|
||||||
const dbBlock = await blocksRepository.$getBlockByHeight(height);
|
const dbBlock = await blocksRepository.$getBlockByHeight(height);
|
||||||
if (dbBlock != null) {
|
if (dbBlock != null) {
|
||||||
return this.prepareBlock(dbBlock);
|
return this.prepareBlock(dbBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
const blockHash = await bitcoinApi.$getBlockHash(height);
|
const blockHash = await bitcoinApi.$getBlockHash(height);
|
||||||
const block = await bitcoinApi.$getBlock(blockHash);
|
const block = BitcoinApi.convertBlock(await bitcoinClient.getBlock(blockHash));
|
||||||
const transactions = await this.$getTransactionsExtended(blockHash, block.height, true);
|
const transactions = await this.$getTransactionsExtended(blockHash, block.height, true);
|
||||||
const blockExtended = await this.$getBlockExtended(block, transactions);
|
const blockExtended = await this.$getBlockExtended(block, transactions);
|
||||||
|
|
||||||
await blocksRepository.$saveBlockInDatabase(blockExtended);
|
await blocksRepository.$saveBlockInDatabase(blockExtended);
|
||||||
|
|
||||||
return blockExtended;
|
return this.prepareBlock(blockExtended);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $getBlocksExtras(fromHeight: number): Promise<BlockExtended[]> {
|
public async $getBlocksExtras(fromHeight: number, limit: number = 15): Promise<BlockExtended[]> {
|
||||||
|
// Note - This API is breaking if indexing is not available. For now it is okay because we only
|
||||||
|
// use it for the mining pages, and mining pages should not be available if indexing is turned off.
|
||||||
|
// I'll need to fix it before we refactor the block(s) related pages
|
||||||
try {
|
try {
|
||||||
loadingIndicators.setProgress('blocks', 0);
|
loadingIndicators.setProgress('blocks', 0);
|
||||||
|
|
||||||
@ -360,10 +373,10 @@ class Blocks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let nextHash = startFromHash;
|
let nextHash = startFromHash;
|
||||||
for (let i = 0; i < 10 && currentHeight >= 0; i++) {
|
for (let i = 0; i < limit && currentHeight >= 0; i++) {
|
||||||
let block = this.getBlocks().find((b) => b.height === currentHeight);
|
let block = this.getBlocks().find((b) => b.height === currentHeight);
|
||||||
if (!block && Common.indexingEnabled()) {
|
if (!block && Common.indexingEnabled()) {
|
||||||
block = this.prepareBlock(await this.$indexBlock(currentHeight));
|
block = await this.$indexBlock(currentHeight);
|
||||||
} else if (!block) {
|
} else if (!block) {
|
||||||
block = this.prepareBlock(await bitcoinApi.$getBlock(nextHash));
|
block = this.prepareBlock(await bitcoinApi.$getBlock(nextHash));
|
||||||
}
|
}
|
||||||
@ -383,24 +396,25 @@ class Blocks {
|
|||||||
private prepareBlock(block: any): BlockExtended {
|
private prepareBlock(block: any): BlockExtended {
|
||||||
return <BlockExtended>{
|
return <BlockExtended>{
|
||||||
id: block.id ?? block.hash, // hash for indexed block
|
id: block.id ?? block.hash, // hash for indexed block
|
||||||
timestamp: block?.timestamp ?? block?.blockTimestamp, // blockTimestamp for indexed block
|
timestamp: block.timestamp ?? block.blockTimestamp, // blockTimestamp for indexed block
|
||||||
height: block?.height,
|
height: block.height,
|
||||||
version: block?.version,
|
version: block.version,
|
||||||
bits: block?.bits,
|
bits: block.bits,
|
||||||
nonce: block?.nonce,
|
nonce: block.nonce,
|
||||||
difficulty: block?.difficulty,
|
difficulty: block.difficulty,
|
||||||
merkle_root: block?.merkle_root,
|
merkle_root: block.merkle_root,
|
||||||
tx_count: block?.tx_count,
|
tx_count: block.tx_count,
|
||||||
size: block?.size,
|
size: block.size,
|
||||||
weight: block?.weight,
|
weight: block.weight,
|
||||||
previousblockhash: block?.previousblockhash,
|
previousblockhash: block.previousblockhash,
|
||||||
extras: {
|
extras: {
|
||||||
medianFee: block?.medianFee,
|
medianFee: block.medianFee ?? block.median_fee ?? block.extras?.medianFee,
|
||||||
feeRange: block?.feeRange ?? [], // TODO
|
feeRange: block.feeRange ?? block.fee_range ?? block?.extras?.feeSpan,
|
||||||
reward: block?.reward,
|
reward: block.reward ?? block?.extras?.reward,
|
||||||
|
totalFees: block.totalFees ?? block?.fees ?? block?.extras.totalFees,
|
||||||
pool: block?.extras?.pool ?? (block?.pool_id ? {
|
pool: block?.extras?.pool ?? (block?.pool_id ? {
|
||||||
id: block?.pool_id,
|
id: block.pool_id,
|
||||||
name: block?.pool_name,
|
name: block.pool_name,
|
||||||
} : undefined),
|
} : undefined),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -6,7 +6,7 @@ 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 {
|
class DatabaseMigration {
|
||||||
private static currentVersion = 12;
|
private static currentVersion = 14;
|
||||||
private queryTimeout = 120000;
|
private queryTimeout = 120000;
|
||||||
private statisticsAddedIndexed = false;
|
private statisticsAddedIndexed = false;
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ class DatabaseMigration {
|
|||||||
await this.$setStatisticsAddedIndexedFlag(databaseSchemaVersion);
|
await this.$setStatisticsAddedIndexedFlag(databaseSchemaVersion);
|
||||||
|
|
||||||
const isBitcoin = ['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK);
|
const isBitcoin = ['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK);
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
try {
|
try {
|
||||||
await this.$executeQuery(connection, this.getCreateElementsTableQuery(), await this.$checkIfTableExists('elements_pegs'));
|
await this.$executeQuery(connection, this.getCreateElementsTableQuery(), await this.$checkIfTableExists('elements_pegs'));
|
||||||
await this.$executeQuery(connection, this.getCreateStatisticsQuery(), await this.$checkIfTableExists('statistics'));
|
await this.$executeQuery(connection, this.getCreateStatisticsQuery(), await this.$checkIfTableExists('statistics'));
|
||||||
@ -161,6 +161,20 @@ class DatabaseMigration {
|
|||||||
await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `fees` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
|
await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `fees` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (databaseSchemaVersion < 13 && isBitcoin === true) {
|
||||||
|
await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `difficulty` DOUBLE UNSIGNED NOT NULL DEFAULT "0"');
|
||||||
|
await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `median_fee` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
|
||||||
|
await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `avg_fee` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
|
||||||
|
await this.$executeQuery(connection, 'ALTER TABLE blocks MODIFY `avg_fee_rate` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (databaseSchemaVersion < 14 && isBitcoin === true) {
|
||||||
|
logger.warn(`'hashrates' table has been truncated. Re-indexing from scratch.`);
|
||||||
|
await this.$executeQuery(connection, 'TRUNCATE hashrates;'); // Need to re-index
|
||||||
|
await this.$executeQuery(connection, 'ALTER TABLE `hashrates` DROP FOREIGN KEY `hashrates_ibfk_1`');
|
||||||
|
await this.$executeQuery(connection, 'ALTER TABLE `hashrates` MODIFY `pool_id` SMALLINT UNSIGNED NOT NULL DEFAULT "0"');
|
||||||
|
}
|
||||||
|
|
||||||
connection.release();
|
connection.release();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -180,7 +194,7 @@ class DatabaseMigration {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// We don't use "CREATE INDEX IF NOT EXISTS" because it is not supported on old mariadb version 5.X
|
// We don't use "CREATE INDEX IF NOT EXISTS" because it is not supported on old mariadb version 5.X
|
||||||
@ -218,7 +232,7 @@ class DatabaseMigration {
|
|||||||
* Check if 'table' exists in the database
|
* Check if 'table' exists in the database
|
||||||
*/
|
*/
|
||||||
private async $checkIfTableExists(table: string): Promise<boolean> {
|
private async $checkIfTableExists(table: string): Promise<boolean> {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const query = `SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '${config.DATABASE.DATABASE}' AND TABLE_NAME = '${table}'`;
|
const query = `SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '${config.DATABASE.DATABASE}' AND TABLE_NAME = '${table}'`;
|
||||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -229,7 +243,7 @@ class DatabaseMigration {
|
|||||||
* Get current database version
|
* Get current database version
|
||||||
*/
|
*/
|
||||||
private async $getSchemaVersionFromDatabase(): Promise<number> {
|
private async $getSchemaVersionFromDatabase(): Promise<number> {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const query = `SELECT number FROM state WHERE name = 'schema_version';`;
|
const query = `SELECT number FROM state WHERE name = 'schema_version';`;
|
||||||
const [rows] = await this.$executeQuery(connection, query, true);
|
const [rows] = await this.$executeQuery(connection, query, true);
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -240,7 +254,7 @@ class DatabaseMigration {
|
|||||||
* Create the `state` table
|
* Create the `state` table
|
||||||
*/
|
*/
|
||||||
private async $createMigrationStateTable(): Promise<void> {
|
private async $createMigrationStateTable(): Promise<void> {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const query = `CREATE TABLE IF NOT EXISTS state (
|
const query = `CREATE TABLE IF NOT EXISTS state (
|
||||||
@ -272,7 +286,7 @@ class DatabaseMigration {
|
|||||||
}
|
}
|
||||||
transactionQueries.push(this.getUpdateToLatestSchemaVersionQuery());
|
transactionQueries.push(this.getUpdateToLatestSchemaVersionQuery());
|
||||||
|
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
try {
|
try {
|
||||||
await this.$executeQuery(connection, 'START TRANSACTION;');
|
await this.$executeQuery(connection, 'START TRANSACTION;');
|
||||||
for (const query of transactionQueries) {
|
for (const query of transactionQueries) {
|
||||||
@ -293,6 +307,7 @@ class DatabaseMigration {
|
|||||||
*/
|
*/
|
||||||
private getMigrationQueriesFromVersion(version: number): string[] {
|
private getMigrationQueriesFromVersion(version: number): string[] {
|
||||||
const queries: string[] = [];
|
const queries: string[] = [];
|
||||||
|
const isBitcoin = ['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK);
|
||||||
|
|
||||||
if (version < 1) {
|
if (version < 1) {
|
||||||
if (config.MEMPOOL.NETWORK !== 'liquid' && config.MEMPOOL.NETWORK !== 'liquidtestnet') {
|
if (config.MEMPOOL.NETWORK !== 'liquid' && config.MEMPOOL.NETWORK !== 'liquidtestnet') {
|
||||||
@ -300,11 +315,11 @@ class DatabaseMigration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version < 7) {
|
if (version < 7 && isBitcoin === true) {
|
||||||
queries.push(`INSERT INTO state(name, number, string) VALUES ('last_hashrates_indexing', 0, NULL)`);
|
queries.push(`INSERT INTO state(name, number, string) VALUES ('last_hashrates_indexing', 0, NULL)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version < 9) {
|
if (version < 9 && isBitcoin === true) {
|
||||||
queries.push(`INSERT INTO state(name, number, string) VALUES ('last_weekly_hashrates_indexing', 0, NULL)`);
|
queries.push(`INSERT INTO state(name, number, string) VALUES ('last_weekly_hashrates_indexing', 0, NULL)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,7 +337,7 @@ class DatabaseMigration {
|
|||||||
* Print current database version
|
* Print current database version
|
||||||
*/
|
*/
|
||||||
private async $printDatabaseVersion() {
|
private async $printDatabaseVersion() {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
try {
|
try {
|
||||||
const [rows] = await this.$executeQuery(connection, 'SELECT VERSION() as version;', true);
|
const [rows] = await this.$executeQuery(connection, 'SELECT VERSION() as version;', true);
|
||||||
logger.debug(`MIGRATIONS: Database engine version '${rows[0].version}'`);
|
logger.debug(`MIGRATIONS: Database engine version '${rows[0].version}'`);
|
||||||
@ -466,7 +481,7 @@ class DatabaseMigration {
|
|||||||
public async $truncateIndexedData(tables: string[]) {
|
public async $truncateIndexedData(tables: string[]) {
|
||||||
const allowedTables = ['blocks', 'hashrates'];
|
const allowedTables = ['blocks', 'hashrates'];
|
||||||
|
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
try {
|
try {
|
||||||
for (const table of tables) {
|
for (const table of tables) {
|
||||||
if (!allowedTables.includes(table)) {
|
if (!allowedTables.includes(table)) {
|
||||||
|
@ -33,7 +33,7 @@ class ElementsParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async $getPegDataByMonth(): Promise<any> {
|
public async $getPegDataByMonth(): Promise<any> {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const query = `SELECT SUM(amount) AS amount, DATE_FORMAT(FROM_UNIXTIME(datetime), '%Y-%m-01') AS date FROM elements_pegs GROUP BY DATE_FORMAT(FROM_UNIXTIME(datetime), '%Y%m')`;
|
const query = `SELECT SUM(amount) AS amount, DATE_FORMAT(FROM_UNIXTIME(datetime), '%Y-%m-01') AS date FROM elements_pegs GROUP BY DATE_FORMAT(FROM_UNIXTIME(datetime), '%Y%m')`;
|
||||||
const [rows] = await connection.query<any>(query);
|
const [rows] = await connection.query<any>(query);
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -79,7 +79,7 @@ class ElementsParser {
|
|||||||
|
|
||||||
protected async $savePegToDatabase(height: number, blockTime: number, amount: number, txid: string,
|
protected async $savePegToDatabase(height: number, blockTime: number, amount: number, txid: string,
|
||||||
txindex: number, bitcoinaddress: string, bitcointxid: string, bitcoinindex: number, final_tx: number): Promise<void> {
|
txindex: number, bitcoinaddress: string, bitcointxid: string, bitcoinindex: number, final_tx: number): Promise<void> {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const query = `INSERT INTO elements_pegs(
|
const query = `INSERT INTO elements_pegs(
|
||||||
block, datetime, amount, txid, txindex, bitcoinaddress, bitcointxid, bitcoinindex, final_tx
|
block, datetime, amount, txid, txindex, bitcoinaddress, bitcointxid, bitcoinindex, final_tx
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`;
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`;
|
||||||
@ -93,7 +93,7 @@ class ElementsParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async $getLatestBlockHeightFromDatabase(): Promise<number> {
|
protected async $getLatestBlockHeightFromDatabase(): Promise<number> {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const query = `SELECT number FROM state WHERE name = 'last_elements_block'`;
|
const query = `SELECT number FROM state WHERE name = 'last_elements_block'`;
|
||||||
const [rows] = await connection.query<any>(query);
|
const [rows] = await connection.query<any>(query);
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -101,7 +101,7 @@ class ElementsParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async $saveLatestBlockToDatabase(blockHeight: number) {
|
protected async $saveLatestBlockToDatabase(blockHeight: number) {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const query = `UPDATE state SET number = ? WHERE name = 'last_elements_block'`;
|
const query = `UPDATE state SET number = ? WHERE name = 'last_elements_block'`;
|
||||||
await connection.query<any>(query, [blockHeight]);
|
await connection.query<any>(query, [blockHeight]);
|
||||||
connection.release();
|
connection.release();
|
||||||
|
@ -80,8 +80,8 @@ class Mining {
|
|||||||
|
|
||||||
// We only run this once a week
|
// We only run this once a week
|
||||||
const latestTimestamp = await HashratesRepository.$getLatestRunTimestamp('last_weekly_hashrates_indexing');
|
const latestTimestamp = await HashratesRepository.$getLatestRunTimestamp('last_weekly_hashrates_indexing');
|
||||||
const now = new Date().getTime() / 1000;
|
const now = new Date();
|
||||||
if (now - latestTimestamp < 604800) {
|
if ((now.getTime() / 1000) - latestTimestamp < 604800) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +94,6 @@ class Mining {
|
|||||||
const hashrates: any[] = [];
|
const hashrates: any[] = [];
|
||||||
const genesisTimestamp = 1231006505; // bitcoin-cli getblock 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
|
const genesisTimestamp = 1231006505; // bitcoin-cli getblock 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
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 = Math.round((lastMondayMidnight.getTime() - 604800) / 1000);
|
let toTimestamp = Math.round((lastMondayMidnight.getTime() - 604800) / 1000);
|
||||||
@ -108,7 +107,7 @@ class Mining {
|
|||||||
const fromTimestamp = toTimestamp - 604800;
|
const fromTimestamp = toTimestamp - 604800;
|
||||||
|
|
||||||
// Skip already indexed weeks
|
// Skip already indexed weeks
|
||||||
if (indexedTimestamp.includes(toTimestamp + 1)) {
|
if (indexedTimestamp.includes(toTimestamp)) {
|
||||||
toTimestamp -= 604800;
|
toTimestamp -= 604800;
|
||||||
++totalIndexed;
|
++totalIndexed;
|
||||||
continue;
|
continue;
|
||||||
@ -133,7 +132,7 @@ class Mining {
|
|||||||
|
|
||||||
for (const pool of pools) {
|
for (const pool of pools) {
|
||||||
hashrates.push({
|
hashrates.push({
|
||||||
hashrateTimestamp: toTimestamp + 1,
|
hashrateTimestamp: toTimestamp,
|
||||||
avgHashrate: pool['hashrate'],
|
avgHashrate: pool['hashrate'],
|
||||||
poolId: pool.poolId,
|
poolId: pool.poolId,
|
||||||
share: pool['share'],
|
share: pool['share'],
|
||||||
@ -202,7 +201,7 @@ class Mining {
|
|||||||
const fromTimestamp = toTimestamp - 86400;
|
const fromTimestamp = toTimestamp - 86400;
|
||||||
|
|
||||||
// Skip already indexed weeks
|
// Skip already indexed weeks
|
||||||
if (indexedTimestamp.includes(fromTimestamp)) {
|
if (indexedTimestamp.includes(toTimestamp)) {
|
||||||
toTimestamp -= 86400;
|
toTimestamp -= 86400;
|
||||||
++totalIndexed;
|
++totalIndexed;
|
||||||
continue;
|
continue;
|
||||||
@ -220,7 +219,7 @@ class Mining {
|
|||||||
hashrates.push({
|
hashrates.push({
|
||||||
hashrateTimestamp: toTimestamp,
|
hashrateTimestamp: toTimestamp,
|
||||||
avgHashrate: lastBlockHashrate,
|
avgHashrate: lastBlockHashrate,
|
||||||
poolId: null,
|
poolId: 0,
|
||||||
share: 1,
|
share: 1,
|
||||||
type: 'daily',
|
type: 'daily',
|
||||||
});
|
});
|
||||||
|
@ -66,7 +66,7 @@ class PoolsParser {
|
|||||||
logger.debug(`Found ${poolNames.length} unique mining pools`);
|
logger.debug(`Found ${poolNames.length} unique mining pools`);
|
||||||
|
|
||||||
// Get existing pools from the db
|
// Get existing pools from the db
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
let existingPools;
|
let existingPools;
|
||||||
try {
|
try {
|
||||||
[existingPools] = await connection.query<any>({ sql: 'SELECT * FROM pools;', timeout: 120000 });
|
[existingPools] = await connection.query<any>({ sql: 'SELECT * FROM pools;', timeout: 120000 });
|
||||||
@ -152,7 +152,7 @@ class PoolsParser {
|
|||||||
* Manually add the 'unknown pool'
|
* Manually add the 'unknown pool'
|
||||||
*/
|
*/
|
||||||
private async insertUnknownPool() {
|
private async insertUnknownPool() {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
try {
|
try {
|
||||||
const [rows]: any[] = await connection.query({ sql: 'SELECT name from pools where name="Unknown"', timeout: 120000 });
|
const [rows]: any[] = await connection.query({ sql: 'SELECT name from pools where name="Unknown"', timeout: 120000 });
|
||||||
if (rows.length === 0) {
|
if (rows.length === 0) {
|
||||||
|
@ -155,7 +155,7 @@ class Statistics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async $createZeroedStatistic(): Promise<number | undefined> {
|
private async $createZeroedStatistic(): Promise<number | undefined> {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
try {
|
try {
|
||||||
const query = `INSERT INTO statistics(
|
const query = `INSERT INTO statistics(
|
||||||
added,
|
added,
|
||||||
@ -216,7 +216,7 @@ class Statistics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async $create(statistics: Statistic): Promise<number | undefined> {
|
private async $create(statistics: Statistic): Promise<number | undefined> {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
try {
|
try {
|
||||||
const query = `INSERT INTO statistics(
|
const query = `INSERT INTO statistics(
|
||||||
added,
|
added,
|
||||||
@ -421,7 +421,7 @@ class Statistics {
|
|||||||
|
|
||||||
private async $get(id: number): Promise<OptimizedStatistic | undefined> {
|
private async $get(id: number): Promise<OptimizedStatistic | undefined> {
|
||||||
try {
|
try {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics WHERE id = ?`;
|
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics WHERE id = ?`;
|
||||||
const [rows] = await connection.query<any>(query, [id]);
|
const [rows] = await connection.query<any>(query, [id]);
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -435,7 +435,7 @@ class Statistics {
|
|||||||
|
|
||||||
public async $list2H(): Promise<OptimizedStatistic[]> {
|
public async $list2H(): Promise<OptimizedStatistic[]> {
|
||||||
try {
|
try {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics ORDER BY statistics.added DESC LIMIT 120`;
|
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics ORDER BY statistics.added DESC LIMIT 120`;
|
||||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -448,7 +448,7 @@ class Statistics {
|
|||||||
|
|
||||||
public async $list24H(): Promise<OptimizedStatistic[]> {
|
public async $list24H(): Promise<OptimizedStatistic[]> {
|
||||||
try {
|
try {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics ORDER BY statistics.added DESC LIMIT 1440`;
|
const query = `SELECT *, UNIX_TIMESTAMP(added) as added FROM statistics ORDER BY statistics.added DESC LIMIT 1440`;
|
||||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -461,7 +461,7 @@ class Statistics {
|
|||||||
|
|
||||||
public async $list1W(): Promise<OptimizedStatistic[]> {
|
public async $list1W(): Promise<OptimizedStatistic[]> {
|
||||||
try {
|
try {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const query = this.getQueryForDaysAvg(300, '1 WEEK'); // 5m interval
|
const query = this.getQueryForDaysAvg(300, '1 WEEK'); // 5m interval
|
||||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -474,7 +474,7 @@ class Statistics {
|
|||||||
|
|
||||||
public async $list1M(): Promise<OptimizedStatistic[]> {
|
public async $list1M(): Promise<OptimizedStatistic[]> {
|
||||||
try {
|
try {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const query = this.getQueryForDaysAvg(1800, '1 MONTH'); // 30m interval
|
const query = this.getQueryForDaysAvg(1800, '1 MONTH'); // 30m interval
|
||||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -487,7 +487,7 @@ class Statistics {
|
|||||||
|
|
||||||
public async $list3M(): Promise<OptimizedStatistic[]> {
|
public async $list3M(): Promise<OptimizedStatistic[]> {
|
||||||
try {
|
try {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const query = this.getQueryForDaysAvg(7200, '3 MONTH'); // 2h interval
|
const query = this.getQueryForDaysAvg(7200, '3 MONTH'); // 2h interval
|
||||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -500,7 +500,7 @@ class Statistics {
|
|||||||
|
|
||||||
public async $list6M(): Promise<OptimizedStatistic[]> {
|
public async $list6M(): Promise<OptimizedStatistic[]> {
|
||||||
try {
|
try {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const query = this.getQueryForDaysAvg(10800, '6 MONTH'); // 3h interval
|
const query = this.getQueryForDaysAvg(10800, '6 MONTH'); // 3h interval
|
||||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -513,7 +513,7 @@ class Statistics {
|
|||||||
|
|
||||||
public async $list1Y(): Promise<OptimizedStatistic[]> {
|
public async $list1Y(): Promise<OptimizedStatistic[]> {
|
||||||
try {
|
try {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const query = this.getQueryForDays(28800, '1 YEAR'); // 8h interval
|
const query = this.getQueryForDays(28800, '1 YEAR'); // 8h interval
|
||||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -526,7 +526,7 @@ class Statistics {
|
|||||||
|
|
||||||
public async $list2Y(): Promise<OptimizedStatistic[]> {
|
public async $list2Y(): Promise<OptimizedStatistic[]> {
|
||||||
try {
|
try {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const query = this.getQueryForDays(28800, "2 YEAR"); // 8h interval
|
const query = this.getQueryForDays(28800, "2 YEAR"); // 8h interval
|
||||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -539,7 +539,7 @@ class Statistics {
|
|||||||
|
|
||||||
public async $list3Y(): Promise<OptimizedStatistic[]> {
|
public async $list3Y(): Promise<OptimizedStatistic[]> {
|
||||||
try {
|
try {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const query = this.getQueryForDays(43200, "3 YEAR"); // 12h interval
|
const query = this.getQueryForDays(43200, "3 YEAR"); // 12h interval
|
||||||
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
const [rows] = await connection.query<any>({ sql: query, timeout: this.queryTimeout });
|
||||||
connection.release();
|
connection.release();
|
||||||
|
@ -43,6 +43,7 @@ interface IConfig {
|
|||||||
DATABASE: {
|
DATABASE: {
|
||||||
ENABLED: boolean;
|
ENABLED: boolean;
|
||||||
HOST: string,
|
HOST: string,
|
||||||
|
SOCKET: string,
|
||||||
PORT: number;
|
PORT: number;
|
||||||
DATABASE: string;
|
DATABASE: string;
|
||||||
USERNAME: string;
|
USERNAME: string;
|
||||||
@ -90,7 +91,7 @@ const defaults: IConfig = {
|
|||||||
'BLOCK_WEIGHT_UNITS': 4000000,
|
'BLOCK_WEIGHT_UNITS': 4000000,
|
||||||
'INITIAL_BLOCKS_AMOUNT': 8,
|
'INITIAL_BLOCKS_AMOUNT': 8,
|
||||||
'MEMPOOL_BLOCKS_AMOUNT': 8,
|
'MEMPOOL_BLOCKS_AMOUNT': 8,
|
||||||
'INDEXING_BLOCKS_AMOUNT': 1100, // 0 = disable indexing, -1 = index all blocks
|
'INDEXING_BLOCKS_AMOUNT': 11000, // 0 = disable indexing, -1 = index all blocks
|
||||||
'PRICE_FEED_UPDATE_INTERVAL': 600,
|
'PRICE_FEED_UPDATE_INTERVAL': 600,
|
||||||
'USE_SECOND_NODE_FOR_MINFEE': false,
|
'USE_SECOND_NODE_FOR_MINFEE': false,
|
||||||
'EXTERNAL_ASSETS': [
|
'EXTERNAL_ASSETS': [
|
||||||
@ -121,6 +122,7 @@ const defaults: IConfig = {
|
|||||||
'DATABASE': {
|
'DATABASE': {
|
||||||
'ENABLED': true,
|
'ENABLED': true,
|
||||||
'HOST': '127.0.0.1',
|
'HOST': '127.0.0.1',
|
||||||
|
'SOCKET': '',
|
||||||
'PORT': 3306,
|
'PORT': 3306,
|
||||||
'DATABASE': 'mempool',
|
'DATABASE': 'mempool',
|
||||||
'USERNAME': 'mempool',
|
'USERNAME': 'mempool',
|
||||||
|
@ -1,23 +1,47 @@
|
|||||||
import config from './config';
|
import config from './config';
|
||||||
import { createPool } from 'mysql2/promise';
|
import { createPool, PoolConnection } from 'mysql2/promise';
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
|
import { PoolOptions } from 'mysql2/typings/mysql';
|
||||||
|
|
||||||
export class DB {
|
export class DB {
|
||||||
static pool = createPool({
|
static poolConfig = ():PoolOptions => {
|
||||||
host: config.DATABASE.HOST,
|
let poolConfig:PoolOptions = {
|
||||||
port: config.DATABASE.PORT,
|
port: config.DATABASE.PORT,
|
||||||
database: config.DATABASE.DATABASE,
|
database: config.DATABASE.DATABASE,
|
||||||
user: config.DATABASE.USERNAME,
|
user: config.DATABASE.USERNAME,
|
||||||
password: config.DATABASE.PASSWORD,
|
password: config.DATABASE.PASSWORD,
|
||||||
connectionLimit: 10,
|
connectionLimit: 10,
|
||||||
supportBigNumbers: true,
|
supportBigNumbers: true,
|
||||||
timezone: '+00:00',
|
timezone: '+00:00',
|
||||||
});
|
}
|
||||||
|
|
||||||
|
if (config.DATABASE.SOCKET !== "") {
|
||||||
|
poolConfig.socketPath = config.DATABASE.SOCKET;
|
||||||
|
} else {
|
||||||
|
poolConfig.host = config.DATABASE.HOST;
|
||||||
|
}
|
||||||
|
|
||||||
|
return poolConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
static pool = createPool(DB.poolConfig());
|
||||||
|
|
||||||
|
static connectionsReady: number[] = [];
|
||||||
|
|
||||||
|
static async getConnection() {
|
||||||
|
const connection: PoolConnection = await DB.pool.getConnection();
|
||||||
|
const connectionId = connection['connection'].connectionId;
|
||||||
|
if (!DB.connectionsReady.includes(connectionId)) {
|
||||||
|
await connection.query(`SET time_zone='+00:00';`);
|
||||||
|
this.connectionsReady.push(connectionId);
|
||||||
|
}
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkDbConnection() {
|
export async function checkDbConnection() {
|
||||||
try {
|
try {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
logger.info('Database connection established.');
|
logger.info('Database connection established.');
|
||||||
connection.release();
|
connection.release();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -5,7 +5,7 @@ import * as WebSocket from 'ws';
|
|||||||
import * as cluster from 'cluster';
|
import * as cluster from 'cluster';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import { checkDbConnection } from './database';
|
import { checkDbConnection, DB } from './database';
|
||||||
import config from './config';
|
import config from './config';
|
||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
import blocks from './api/blocks';
|
import blocks from './api/blocks';
|
||||||
@ -97,8 +97,10 @@ class Server {
|
|||||||
await databaseMigration.$truncateIndexedData(tables);
|
await databaseMigration.$truncateIndexedData(tables);
|
||||||
}
|
}
|
||||||
await databaseMigration.$initializeOrMigrateDatabase();
|
await databaseMigration.$initializeOrMigrateDatabase();
|
||||||
await this.$resetHashratesIndexingState();
|
if (Common.indexingEnabled()) {
|
||||||
await poolsParser.migratePoolsJson();
|
await this.$resetHashratesIndexingState();
|
||||||
|
await poolsParser.migratePoolsJson();
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(e instanceof Error ? e.message : 'Error');
|
throw new Error(e instanceof Error ? e.message : 'Error');
|
||||||
}
|
}
|
||||||
@ -178,8 +180,8 @@ class Server {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
blocks.$generateBlockDatabase();
|
blocks.$generateBlockDatabase();
|
||||||
mining.$generateNetworkHashrateHistory();
|
await mining.$generateNetworkHashrateHistory();
|
||||||
mining.$generatePoolHashrateHistory();
|
await mining.$generatePoolHashrateHistory();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.err(`Unable to run indexing right now, trying again later. ` + e);
|
logger.err(`Unable to run indexing right now, trying again later. ` + e);
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ class BlocksRepository {
|
|||||||
* Save indexed block data in the database
|
* Save indexed block data in the database
|
||||||
*/
|
*/
|
||||||
public async $saveBlockInDatabase(block: BlockExtended) {
|
public async $saveBlockInDatabase(block: BlockExtended) {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const query = `INSERT INTO blocks(
|
const query = `INSERT INTO blocks(
|
||||||
@ -70,7 +70,7 @@ class BlocksRepository {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
try {
|
try {
|
||||||
const [rows]: any[] = await connection.query(`
|
const [rows]: any[] = await connection.query(`
|
||||||
SELECT height
|
SELECT height
|
||||||
@ -116,7 +116,7 @@ class BlocksRepository {
|
|||||||
|
|
||||||
query += ` GROUP by pools.id`;
|
query += ` GROUP by pools.id`;
|
||||||
|
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
try {
|
try {
|
||||||
const [rows] = await connection.query(query, params);
|
const [rows] = await connection.query(query, params);
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -154,7 +154,7 @@ class BlocksRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// logger.debug(query);
|
// logger.debug(query);
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
try {
|
try {
|
||||||
const [rows] = await connection.query(query, params);
|
const [rows] = await connection.query(query, params);
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -194,7 +194,7 @@ class BlocksRepository {
|
|||||||
query += ` blockTimestamp BETWEEN FROM_UNIXTIME('${from}') AND FROM_UNIXTIME('${to}')`;
|
query += ` blockTimestamp BETWEEN FROM_UNIXTIME('${from}') AND FROM_UNIXTIME('${to}')`;
|
||||||
|
|
||||||
// logger.debug(query);
|
// logger.debug(query);
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
try {
|
try {
|
||||||
const [rows] = await connection.query(query, params);
|
const [rows] = await connection.query(query, params);
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -217,7 +217,7 @@ class BlocksRepository {
|
|||||||
LIMIT 1;`;
|
LIMIT 1;`;
|
||||||
|
|
||||||
// logger.debug(query);
|
// logger.debug(query);
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
try {
|
try {
|
||||||
const [rows]: any[] = await connection.query(query);
|
const [rows]: any[] = await connection.query(query);
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -253,7 +253,7 @@ class BlocksRepository {
|
|||||||
LIMIT 10`;
|
LIMIT 10`;
|
||||||
|
|
||||||
// logger.debug(query);
|
// logger.debug(query);
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
try {
|
try {
|
||||||
const [rows] = await connection.query(query, params);
|
const [rows] = await connection.query(query, params);
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -274,10 +274,13 @@ class BlocksRepository {
|
|||||||
* Get one block by height
|
* Get one block by height
|
||||||
*/
|
*/
|
||||||
public async $getBlockByHeight(height: number): Promise<object | null> {
|
public async $getBlockByHeight(height: number): Promise<object | null> {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
try {
|
try {
|
||||||
const [rows]: any[] = await connection.query(`
|
const [rows]: any[] = await connection.query(`
|
||||||
SELECT *, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp, pools.id as pool_id, pools.name as pool_name, pools.link as pool_link, pools.addresses as pool_addresses, pools.regexes as pool_regexes
|
SELECT *, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp,
|
||||||
|
pools.id as pool_id, pools.name as pool_name, pools.link as pool_link,
|
||||||
|
pools.addresses as pool_addresses, pools.regexes as pool_regexes,
|
||||||
|
previous_block_hash as previousblockhash
|
||||||
FROM blocks
|
FROM blocks
|
||||||
JOIN pools ON blocks.pool_id = pools.id
|
JOIN pools ON blocks.pool_id = pools.id
|
||||||
WHERE height = ${height};
|
WHERE height = ${height};
|
||||||
@ -302,7 +305,7 @@ class BlocksRepository {
|
|||||||
public async $getBlocksDifficulty(interval: string | null): Promise<object[]> {
|
public async $getBlocksDifficulty(interval: string | null): Promise<object[]> {
|
||||||
interval = Common.getSqlInterval(interval);
|
interval = Common.getSqlInterval(interval);
|
||||||
|
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
|
|
||||||
// :D ... Yeah don't ask me about this one https://stackoverflow.com/a/40303162
|
// :D ... Yeah don't ask me about this one https://stackoverflow.com/a/40303162
|
||||||
// Basically, using temporary user defined fields, we are able to extract all
|
// Basically, using temporary user defined fields, we are able to extract all
|
||||||
@ -353,7 +356,7 @@ class BlocksRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async $getOldestIndexedBlockHeight(): Promise<number> {
|
public async $getOldestIndexedBlockHeight(): Promise<number> {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
try {
|
try {
|
||||||
const [rows]: any[] = await connection.query(`SELECT MIN(height) as minHeight FROM blocks`);
|
const [rows]: any[] = await connection.query(`SELECT MIN(height) as minHeight FROM blocks`);
|
||||||
connection.release();
|
connection.release();
|
||||||
|
@ -20,9 +20,9 @@ class HashratesRepository {
|
|||||||
}
|
}
|
||||||
query = query.slice(0, -1);
|
query = query.slice(0, -1);
|
||||||
|
|
||||||
const connection = await DB.pool.getConnection();
|
let connection;
|
||||||
try {
|
try {
|
||||||
// logger.debug(query);
|
connection = await DB.getConnection();
|
||||||
await connection.query(query);
|
await connection.query(query);
|
||||||
connection.release();
|
connection.release();
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@ -35,18 +35,16 @@ class HashratesRepository {
|
|||||||
public async $getNetworkDailyHashrate(interval: string | null): Promise<any[]> {
|
public async $getNetworkDailyHashrate(interval: string | null): Promise<any[]> {
|
||||||
interval = Common.getSqlInterval(interval);
|
interval = Common.getSqlInterval(interval);
|
||||||
|
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
|
|
||||||
let query = `SELECT UNIX_TIMESTAMP(hashrate_timestamp) as timestamp, avg_hashrate as avgHashrate
|
let query = `SELECT UNIX_TIMESTAMP(hashrate_timestamp) as timestamp, avg_hashrate as avgHashrate
|
||||||
FROM hashrates`;
|
FROM hashrates`;
|
||||||
|
|
||||||
if (interval) {
|
if (interval) {
|
||||||
query += ` WHERE hashrate_timestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()
|
query += ` WHERE hashrate_timestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()
|
||||||
AND hashrates.type = 'daily'
|
AND hashrates.type = 'daily'`;
|
||||||
AND pool_id IS NULL`;
|
|
||||||
} else {
|
} else {
|
||||||
query += ` WHERE hashrates.type = 'daily'
|
query += ` WHERE hashrates.type = 'daily'`;
|
||||||
AND pool_id IS NULL`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
query += ` ORDER by hashrate_timestamp`;
|
query += ` ORDER by hashrate_timestamp`;
|
||||||
@ -64,9 +62,12 @@ class HashratesRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async $getWeeklyHashrateTimestamps(): Promise<number[]> {
|
public async $getWeeklyHashrateTimestamps(): Promise<number[]> {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
|
|
||||||
const query = `SELECT UNIX_TIMESTAMP(hashrate_timestamp) as timestamp FROM hashrates where type = 'weekly' GROUP BY hashrate_timestamp`;
|
const query = `SELECT UNIX_TIMESTAMP(hashrate_timestamp) as timestamp
|
||||||
|
FROM hashrates
|
||||||
|
WHERE type = 'weekly'
|
||||||
|
GROUP BY hashrate_timestamp`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [rows]: any[] = await connection.query(query);
|
const [rows]: any[] = await connection.query(query);
|
||||||
@ -86,7 +87,7 @@ class HashratesRepository {
|
|||||||
public async $getPoolsWeeklyHashrate(interval: string | null): Promise<any[]> {
|
public async $getPoolsWeeklyHashrate(interval: string | null): Promise<any[]> {
|
||||||
interval = Common.getSqlInterval(interval);
|
interval = Common.getSqlInterval(interval);
|
||||||
|
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const topPoolsId = (await PoolsRepository.$getPoolsInfo('1w')).map((pool) => pool.poolId);
|
const topPoolsId = (await PoolsRepository.$getPoolsInfo('1w')).map((pool) => pool.poolId);
|
||||||
|
|
||||||
let query = `SELECT UNIX_TIMESTAMP(hashrate_timestamp) as timestamp, avg_hashrate as avgHashrate, share, pools.name as poolName
|
let query = `SELECT UNIX_TIMESTAMP(hashrate_timestamp) as timestamp, avg_hashrate as avgHashrate, share, pools.name as poolName
|
||||||
@ -120,7 +121,7 @@ class HashratesRepository {
|
|||||||
* Returns a pool hashrate history
|
* Returns a pool hashrate history
|
||||||
*/
|
*/
|
||||||
public async $getPoolWeeklyHashrate(poolId: number): Promise<any[]> {
|
public async $getPoolWeeklyHashrate(poolId: number): Promise<any[]> {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
|
|
||||||
// Find hashrate boundaries
|
// Find hashrate boundaries
|
||||||
let query = `SELECT MIN(hashrate_timestamp) as firstTimestamp, MAX(hashrate_timestamp) as lastTimestamp
|
let query = `SELECT MIN(hashrate_timestamp) as firstTimestamp, MAX(hashrate_timestamp) as lastTimestamp
|
||||||
@ -163,7 +164,7 @@ class HashratesRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async $setLatestRunTimestamp(key: string, val: any = null) {
|
public async $setLatestRunTimestamp(key: string, val: any = null) {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const query = `UPDATE state SET number = ? WHERE name = ?`;
|
const query = `UPDATE state SET number = ? WHERE name = ?`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -175,7 +176,7 @@ class HashratesRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async $getLatestRunTimestamp(key: string): Promise<number> {
|
public async $getLatestRunTimestamp(key: string): Promise<number> {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const query = `SELECT number FROM state WHERE name = ?`;
|
const query = `SELECT number FROM state WHERE name = ?`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -8,7 +8,7 @@ class PoolsRepository {
|
|||||||
* Get all pools tagging info
|
* Get all pools tagging info
|
||||||
*/
|
*/
|
||||||
public async $getPools(): Promise<PoolTag[]> {
|
public async $getPools(): Promise<PoolTag[]> {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const [rows] = await connection.query('SELECT id, name, addresses, regexes FROM pools;');
|
const [rows] = await connection.query('SELECT id, name, addresses, regexes FROM pools;');
|
||||||
connection.release();
|
connection.release();
|
||||||
return <PoolTag[]>rows;
|
return <PoolTag[]>rows;
|
||||||
@ -18,7 +18,7 @@ class PoolsRepository {
|
|||||||
* Get unknown pool tagging info
|
* Get unknown pool tagging info
|
||||||
*/
|
*/
|
||||||
public async $getUnknownPool(): Promise<PoolTag> {
|
public async $getUnknownPool(): Promise<PoolTag> {
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
const [rows] = await connection.query('SELECT id, name FROM pools where name = "Unknown"');
|
const [rows] = await connection.query('SELECT id, name FROM pools where name = "Unknown"');
|
||||||
connection.release();
|
connection.release();
|
||||||
return <PoolTag>rows[0];
|
return <PoolTag>rows[0];
|
||||||
@ -42,7 +42,7 @@ class PoolsRepository {
|
|||||||
ORDER BY COUNT(height) DESC`;
|
ORDER BY COUNT(height) DESC`;
|
||||||
|
|
||||||
// logger.debug(query);
|
// logger.debug(query);
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
try {
|
try {
|
||||||
const [rows] = await connection.query(query);
|
const [rows] = await connection.query(query);
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -64,7 +64,7 @@ class PoolsRepository {
|
|||||||
LEFT JOIN blocks on pools.id = blocks.pool_id AND blocks.blockTimestamp BETWEEN FROM_UNIXTIME(?) AND FROM_UNIXTIME(?)
|
LEFT JOIN blocks on pools.id = blocks.pool_id AND blocks.blockTimestamp BETWEEN FROM_UNIXTIME(?) AND FROM_UNIXTIME(?)
|
||||||
GROUP BY pools.id`;
|
GROUP BY pools.id`;
|
||||||
|
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
try {
|
try {
|
||||||
const [rows] = await connection.query(query, [from, to]);
|
const [rows] = await connection.query(query, [from, to]);
|
||||||
connection.release();
|
connection.release();
|
||||||
@ -87,7 +87,7 @@ class PoolsRepository {
|
|||||||
WHERE pools.id = ?`;
|
WHERE pools.id = ?`;
|
||||||
|
|
||||||
// logger.debug(query);
|
// logger.debug(query);
|
||||||
const connection = await DB.pool.getConnection();
|
const connection = await DB.getConnection();
|
||||||
try {
|
try {
|
||||||
const [rows] = await connection.query(query, [poolId]);
|
const [rows] = await connection.query(query, [poolId]);
|
||||||
connection.release();
|
connection.release();
|
||||||
|
@ -658,7 +658,7 @@ class Routes {
|
|||||||
|
|
||||||
public async getBlocksExtras(req: Request, res: Response) {
|
public async getBlocksExtras(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
res.json(await blocks.$getBlocksExtras(parseInt(req.params.height, 10)))
|
res.json(await blocks.$getBlocksExtras(parseInt(req.params.height, 10), 15));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
res.status(500).send(e instanceof Error ? e.message : e);
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
}
|
}
|
||||||
|
3
contributors/dsbaars.txt
Normal file
3
contributors/dsbaars.txt
Normal file
@ -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: dsbaars
|
@ -41,6 +41,7 @@
|
|||||||
"DATABASE": {
|
"DATABASE": {
|
||||||
"ENABLED": __DATABASE_ENABLED__,
|
"ENABLED": __DATABASE_ENABLED__,
|
||||||
"HOST": "__DATABASE_HOST__",
|
"HOST": "__DATABASE_HOST__",
|
||||||
|
"SOCKET": "__DATABASE_SOCKET__",
|
||||||
"PORT": __DATABASE_PORT__,
|
"PORT": __DATABASE_PORT__,
|
||||||
"DATABASE": "__DATABASE_DATABASE__",
|
"DATABASE": "__DATABASE_DATABASE__",
|
||||||
"USERNAME": "__DATABASE_USERNAME__",
|
"USERNAME": "__DATABASE_USERNAME__",
|
||||||
|
@ -13,7 +13,7 @@ __MEMPOOL_RECOMMENDED_FEE_PERCENTILE__=${MEMPOOL_RECOMMENDED_FEE_PERCENTILE:=50}
|
|||||||
__MEMPOOL_BLOCK_WEIGHT_UNITS__=${MEMPOOL_BLOCK_WEIGHT_UNITS:=4000000}
|
__MEMPOOL_BLOCK_WEIGHT_UNITS__=${MEMPOOL_BLOCK_WEIGHT_UNITS:=4000000}
|
||||||
__MEMPOOL_INITIAL_BLOCKS_AMOUNT__=${MEMPOOL_INITIAL_BLOCKS_AMOUNT:=8}
|
__MEMPOOL_INITIAL_BLOCKS_AMOUNT__=${MEMPOOL_INITIAL_BLOCKS_AMOUNT:=8}
|
||||||
__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__=${MEMPOOL_MEMPOOL_BLOCKS_AMOUNT:=8}
|
__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__=${MEMPOOL_MEMPOOL_BLOCKS_AMOUNT:=8}
|
||||||
__MEMPOOL_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=1100}
|
__MEMPOOL_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=11000}
|
||||||
__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__=${MEMPOOL_PRICE_FEED_UPDATE_INTERVAL:=600}
|
__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__=${MEMPOOL_PRICE_FEED_UPDATE_INTERVAL:=600}
|
||||||
__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__=${MEMPOOL_USE_SECOND_NODE_FOR_MINFEE:=false}
|
__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__=${MEMPOOL_USE_SECOND_NODE_FOR_MINFEE:=false}
|
||||||
__MEMPOOL_EXTERNAL_ASSETS__=${MEMPOOL_EXTERNAL_ASSETS:=[\"https://mempool.space/resources/pools.json\"]}
|
__MEMPOOL_EXTERNAL_ASSETS__=${MEMPOOL_EXTERNAL_ASSETS:=[\"https://mempool.space/resources/pools.json\"]}
|
||||||
@ -42,6 +42,7 @@ __SECOND_CORE_RPC_PASSWORD__=${SECOND_CORE_RPC_PASSWORD:=mempool}
|
|||||||
# DATABASE
|
# DATABASE
|
||||||
__DATABASE_ENABLED__=${DATABASE_ENABLED:=true}
|
__DATABASE_ENABLED__=${DATABASE_ENABLED:=true}
|
||||||
__DATABASE_HOST__=${DATABASE_HOST:=127.0.0.1}
|
__DATABASE_HOST__=${DATABASE_HOST:=127.0.0.1}
|
||||||
|
__DATABASE_SOCKET__=${DATABASE_SOCKET:=""}
|
||||||
__DATABASE_PORT__=${DATABASE_PORT:=3306}
|
__DATABASE_PORT__=${DATABASE_PORT:=3306}
|
||||||
__DATABASE_DATABASE__=${DATABASE_DATABASE:=mempool}
|
__DATABASE_DATABASE__=${DATABASE_DATABASE:=mempool}
|
||||||
__DATABASE_USERNAME__=${DATABASE_USERNAME:=mempool}
|
__DATABASE_USERNAME__=${DATABASE_USERNAME:=mempool}
|
||||||
@ -111,6 +112,8 @@ sed -i "s/__SECOND_CORE_RPC_PASSWORD__/${__SECOND_CORE_RPC_PASSWORD__}/g" mempoo
|
|||||||
|
|
||||||
sed -i "s/__DATABASE_ENABLED__/${__DATABASE_ENABLED__}/g" mempool-config.json
|
sed -i "s/__DATABASE_ENABLED__/${__DATABASE_ENABLED__}/g" mempool-config.json
|
||||||
sed -i "s/__DATABASE_HOST__/${__DATABASE_HOST__}/g" mempool-config.json
|
sed -i "s/__DATABASE_HOST__/${__DATABASE_HOST__}/g" mempool-config.json
|
||||||
|
sed -i "s!__DATABASE_SOCKET__!${__DATABASE_SOCKET__}!g" mempool-config.json
|
||||||
|
|
||||||
sed -i "s/__DATABASE_PORT__/${__DATABASE_PORT__}/g" mempool-config.json
|
sed -i "s/__DATABASE_PORT__/${__DATABASE_PORT__}/g" mempool-config.json
|
||||||
sed -i "s/__DATABASE_DATABASE__/${__DATABASE_DATABASE__}/g" mempool-config.json
|
sed -i "s/__DATABASE_DATABASE__/${__DATABASE_DATABASE__}/g" mempool-config.json
|
||||||
sed -i "s/__DATABASE_USERNAME__/${__DATABASE_USERNAME__}/g" mempool-config.json
|
sed -i "s/__DATABASE_USERNAME__/${__DATABASE_USERNAME__}/g" mempool-config.json
|
||||||
|
@ -31,6 +31,7 @@ import { MiningDashboardComponent } from './components/mining-dashboard/mining-d
|
|||||||
import { HashrateChartComponent } from './components/hashrate-chart/hashrate-chart.component';
|
import { HashrateChartComponent } from './components/hashrate-chart/hashrate-chart.component';
|
||||||
import { HashrateChartPoolsComponent } from './components/hashrates-chart-pools/hashrate-chart-pools.component';
|
import { HashrateChartPoolsComponent } from './components/hashrates-chart-pools/hashrate-chart-pools.component';
|
||||||
import { MiningStartComponent } from './components/mining-start/mining-start.component';
|
import { MiningStartComponent } from './components/mining-start/mining-start.component';
|
||||||
|
import { BlocksList } from './components/blocks-list/blocks-list.component';
|
||||||
|
|
||||||
let routes: Routes = [
|
let routes: Routes = [
|
||||||
{
|
{
|
||||||
@ -75,6 +76,10 @@ let routes: Routes = [
|
|||||||
path: 'mining',
|
path: 'mining',
|
||||||
component: MiningStartComponent,
|
component: MiningStartComponent,
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
path: 'blocks',
|
||||||
|
component: BlocksList,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'hashrate',
|
path: 'hashrate',
|
||||||
component: HashrateChartComponent,
|
component: HashrateChartComponent,
|
||||||
@ -190,6 +195,10 @@ let routes: Routes = [
|
|||||||
path: 'mining',
|
path: 'mining',
|
||||||
component: MiningStartComponent,
|
component: MiningStartComponent,
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
path: 'blocks',
|
||||||
|
component: BlocksList,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'hashrate',
|
path: 'hashrate',
|
||||||
component: HashrateChartComponent,
|
component: HashrateChartComponent,
|
||||||
@ -299,6 +308,10 @@ let routes: Routes = [
|
|||||||
path: 'mining',
|
path: 'mining',
|
||||||
component: MiningStartComponent,
|
component: MiningStartComponent,
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
path: 'blocks',
|
||||||
|
component: BlocksList,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'hashrate',
|
path: 'hashrate',
|
||||||
component: HashrateChartComponent,
|
component: HashrateChartComponent,
|
||||||
@ -630,7 +643,7 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
|
|||||||
initialNavigation: 'enabled',
|
initialNavigation: 'enabled',
|
||||||
scrollPositionRestoration: 'enabled',
|
scrollPositionRestoration: 'enabled',
|
||||||
anchorScrolling: 'enabled'
|
anchorScrolling: 'enabled'
|
||||||
})],
|
})],
|
||||||
exports: [RouterModule]
|
exports: [RouterModule]
|
||||||
})
|
})
|
||||||
export class AppRoutingModule { }
|
export class AppRoutingModule { }
|
||||||
|
@ -76,6 +76,7 @@ import { MiningStartComponent } from './components/mining-start/mining-start.com
|
|||||||
import { AmountShortenerPipe } from './shared/pipes/amount-shortener.pipe';
|
import { AmountShortenerPipe } from './shared/pipes/amount-shortener.pipe';
|
||||||
import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe';
|
import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe';
|
||||||
import { DifficultyAdjustmentsTable } from './components/difficulty-adjustments-table/difficulty-adjustments-table.components';
|
import { DifficultyAdjustmentsTable } from './components/difficulty-adjustments-table/difficulty-adjustments-table.components';
|
||||||
|
import { BlocksList } from './components/blocks-list/blocks-list.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -133,6 +134,7 @@ import { DifficultyAdjustmentsTable } from './components/difficulty-adjustments-
|
|||||||
MiningStartComponent,
|
MiningStartComponent,
|
||||||
AmountShortenerPipe,
|
AmountShortenerPipe,
|
||||||
DifficultyAdjustmentsTable,
|
DifficultyAdjustmentsTable,
|
||||||
|
BlocksList,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule.withServerTransition({ appId: 'serverApp' }),
|
BrowserModule.withServerTransition({ appId: 'serverApp' }),
|
||||||
|
@ -170,13 +170,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="community-integrations-sponsor">
|
<div class="community-integrations-sponsor">
|
||||||
<h3 i18n="about.integrations">Community Integrations</h3>
|
<h3 i18n="about.self-hosted-integrations">Self-Hosted Integrations</h3>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<a href="https://github.com/bisq-network/bisq" target="_blank" title="Bisq">
|
|
||||||
<img class="image" src="/resources/profile/bisq_network.png" />
|
|
||||||
<span>Bisq</span>
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/getumbrel/umbrel" target="_blank" title="Umbrel">
|
<a href="https://github.com/getumbrel/umbrel" target="_blank" title="Umbrel">
|
||||||
<img class="image" src="/resources/profile/umbrel.png" />
|
<img class="image" src="/resources/profile/umbrel.png" />
|
||||||
<span>Umbrel</span>
|
<span>Umbrel</span>
|
||||||
@ -201,6 +196,20 @@
|
|||||||
<img class="image" src="/resources/profile/nix-bitcoin.png" />
|
<img class="image" src="/resources/profile/nix-bitcoin.png" />
|
||||||
<span>NixOS</span>
|
<span>NixOS</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://github.com/Start9Labs/embassy-os" target="_blank" title="EmbassyOS">
|
||||||
|
<img class="image" src="/resources/profile/start9.png" />
|
||||||
|
<span>EmbassyOS</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="community-integrations-sponsor">
|
||||||
|
<h3 i18n="about.wallet-integrations">Wallet Integrations</h3>
|
||||||
|
<div class="wrapper">
|
||||||
|
<a href="https://github.com/bisq-network/bisq" target="_blank" title="Bisq">
|
||||||
|
<img class="image" src="/resources/profile/bisq_network.png" />
|
||||||
|
<span>Bisq</span>
|
||||||
|
</a>
|
||||||
<a href="https://github.com/spesmilo/electrum" target="_blank" title="Electrum Wallet">
|
<a href="https://github.com/spesmilo/electrum" target="_blank" title="Electrum Wallet">
|
||||||
<img class="image" src="/resources/profile/electrum.jpg" />
|
<img class="image" src="/resources/profile/electrum.jpg" />
|
||||||
<span>Electrum</span>
|
<span>Electrum</span>
|
||||||
|
@ -62,7 +62,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<app-transactions-list [transactions]="transactions" [showConfirmations]="true" (loadMore)="loadMore()"></app-transactions-list>
|
<app-transactions-list [transactions]="transactions" [showConfirmations]="true" [address]="address.address" (loadMore)="loadMore()"></app-transactions-list>
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<ng-template [ngIf]="isLoadingTransactions">
|
<ng-template [ngIf]="isLoadingTransactions">
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<ng-container *ngIf="!noFiat && (viewFiat$ | async) && (conversions$ | async) as conversions; else viewFiatVin">
|
<ng-container *ngIf="!noFiat && (viewFiat$ | async) && (conversions$ | async) as conversions; else viewFiatVin">
|
||||||
<span class="fiat">{{ conversions.USD * (satoshis / 100000000) | currency:'USD':'symbol':'1.2-2' }}</span>
|
<span class="fiat">{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ conversions.USD * (satoshis / 100000000) | currency:'USD':'symbol':'1.2-2' }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #viewFiatVin>
|
<ng-template #viewFiatVin>
|
||||||
<ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && (satoshis === undefined || satoshis === null)" [ngIfElse]="default">
|
<ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && (satoshis === undefined || satoshis === null)" [ngIfElse]="default">
|
||||||
<span i18n="shared.confidential">Confidential</span>
|
<span i18n="shared.confidential">Confidential</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #default>
|
<ng-template #default>
|
||||||
‎{{ satoshis / 100000000 | number : digitsInfo }}
|
‎{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ satoshis / 100000000 | number : digitsInfo }}
|
||||||
<span class="symbol"><ng-template [ngIf]="network === 'liquid'">L-</ng-template>
|
<span class="symbol"><ng-template [ngIf]="network === 'liquid'">L-</ng-template>
|
||||||
<ng-template [ngIf]="network === 'liquidtestnet'">tL-</ng-template>
|
<ng-template [ngIf]="network === 'liquidtestnet'">tL-</ng-template>
|
||||||
<ng-template [ngIf]="network === 'testnet'">t</ng-template>
|
<ng-template [ngIf]="network === 'testnet'">t</ng-template>
|
||||||
|
@ -18,6 +18,7 @@ export class AmountComponent implements OnInit, OnDestroy {
|
|||||||
@Input() satoshis: number;
|
@Input() satoshis: number;
|
||||||
@Input() digitsInfo = '1.8-8';
|
@Input() digitsInfo = '1.8-8';
|
||||||
@Input() noFiat = false;
|
@Input() noFiat = false;
|
||||||
|
@Input() addPlus = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
|
@ -0,0 +1,96 @@
|
|||||||
|
<div class="container-xl" [class]="widget ? 'widget' : ''">
|
||||||
|
<h1 *ngIf="!widget" class="float-left" i18n="latest-blocks.blocks">Blocks</h1>
|
||||||
|
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
|
<div style="min-height: 295px">
|
||||||
|
<table class="table table-borderless">
|
||||||
|
<thead>
|
||||||
|
<th class="height" [class]="widget ? 'widget' : ''" i18n="latest-blocks.height">Height</th>
|
||||||
|
<th class="pool text-left" [class]="widget ? 'widget' : ''" i18n="latest-blocks.mined-by">
|
||||||
|
Pool</th>
|
||||||
|
<th class="timestamp" i18n="latest-blocks.timestamp" *ngIf="!widget">Timestamp</th>
|
||||||
|
<th class="mined" i18n="latest-blocks.mined" *ngIf="!widget">Mined</th>
|
||||||
|
<th class="reward text-right" i18n="latest-blocks.reward" [class]="widget ? 'widget' : ''">
|
||||||
|
Reward</th>
|
||||||
|
<th class="fees text-right" i18n="latest-blocks.fees" *ngIf="!widget">Fees</th>
|
||||||
|
<th class="txs text-right" i18n="latest-blocks.transactions" [class]="widget ? 'widget' : ''">Txs</th>
|
||||||
|
<th class="size" i18n="latest-blocks.size" *ngIf="!widget">Size</th>
|
||||||
|
</thead>
|
||||||
|
<tbody *ngIf="blocks$ | async as blocks; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
|
||||||
|
<tr *ngFor="let block of blocks; let i= index; trackBy: trackByBlock">
|
||||||
|
<td class="height " [class]="widget ? 'widget' : ''">
|
||||||
|
<a [routerLink]="['/block' | relativeUrl, block.height]">{{ block.height
|
||||||
|
}}</a>
|
||||||
|
</td>
|
||||||
|
<td class="pool text-left" [class]="widget ? 'widget' : ''">
|
||||||
|
<a class="clear-link" [routerLink]="[('/mining/pool/' + block.extras.pool.id) | relativeUrl]">
|
||||||
|
<img width="25" height="25" src="{{ block.extras.pool['logo'] }}"
|
||||||
|
onError="this.src = './resources/mining-pools/default.svg'">
|
||||||
|
<span class="pool-name">{{ block.extras.pool.name }}</span>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="timestamp" *ngIf="!widget">
|
||||||
|
‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}
|
||||||
|
</td>
|
||||||
|
<td class="mined" *ngIf="!widget">
|
||||||
|
<app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since>
|
||||||
|
</td>
|
||||||
|
<td class="reward text-right" [class]="widget ? 'widget' : ''">
|
||||||
|
<app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-2"></app-amount>
|
||||||
|
</td>
|
||||||
|
<td class="fees text-right" *ngIf="!widget">
|
||||||
|
<app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-2"></app-amount>
|
||||||
|
</td>
|
||||||
|
<td class="txs text-right" [class]="widget ? 'widget' : ''">
|
||||||
|
{{ block.tx_count | number }}
|
||||||
|
</td>
|
||||||
|
<td class="size" *ngIf="!widget">
|
||||||
|
<div class="progress">
|
||||||
|
<div class="progress-bar progress-mempool" role="progressbar"
|
||||||
|
[ngStyle]="{'width': (block.weight / stateService.env.BLOCK_WEIGHT_UNITS)*100 + '%' }"></div>
|
||||||
|
<div class="progress-text" [innerHTML]="block.size | bytes: 2"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<ng-template #skeleton>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let item of skeletonLines">
|
||||||
|
<td class="height" [class]="widget ? 'widget' : ''">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
<td class="pool text-left" [class]="widget ? 'widget' : ''">
|
||||||
|
<img width="0" height="25" style="opacity: 0">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
<td class="timestamp" *ngIf="!widget">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
<td class="mined" *ngIf="!widget">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
<td class="reward text-right" [class]="widget ? 'widget' : ''">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
<td class="fees text-right" *ngIf="!widget">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
<td class="txs text-right" [class]="widget ? 'widget' : ''">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
<td class="size" *ngIf="!widget">
|
||||||
|
<span class="skeleton-loader"></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</ng-template>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<ngb-pagination *ngIf="!widget" class="pagination-container float-right mt-2" [class]="isLoading ? 'disabled' : ''"
|
||||||
|
[collectionSize]="blocksCount" [rotate]="true" [maxSize]="5" [pageSize]="15" [(page)]="page"
|
||||||
|
(pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false">
|
||||||
|
</ngb-pagination>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
@ -0,0 +1,124 @@
|
|||||||
|
.container-xl {
|
||||||
|
max-width: 1400px;
|
||||||
|
padding-bottom: 100px;
|
||||||
|
}
|
||||||
|
.container-xl.widget {
|
||||||
|
padding-left: 0px;
|
||||||
|
padding-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding-top: 0.7rem !important;
|
||||||
|
padding-bottom: 0.7rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-link {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
background-color: #2d3348;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pool {
|
||||||
|
width: 17%;
|
||||||
|
}
|
||||||
|
.pool.widget {
|
||||||
|
width: 40%;
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
padding-left: 30px;
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pool-name {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: text-top;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.height {
|
||||||
|
width: 10%;
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
width: 10%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.height.widget {
|
||||||
|
width: 20%;
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
width: 10%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timestamp {
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mined {
|
||||||
|
width: 13%;
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.txs {
|
||||||
|
padding-right: 40px;
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
@media (max-width: 875px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.txs.widget {
|
||||||
|
padding-right: 0;
|
||||||
|
@media (max-width: 650px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fees {
|
||||||
|
@media (max-width: 650px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fees.widget {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reward {
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
width: 7%;
|
||||||
|
padding-right: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.reward.widget {
|
||||||
|
width: 20%;
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
width: 30%;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.size {
|
||||||
|
width: 12%;
|
||||||
|
@media (max-width: 1000px) {
|
||||||
|
width: 15%;
|
||||||
|
}
|
||||||
|
@media (max-width: 650px) {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
@media (max-width: 450px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||||
|
import { BehaviorSubject, combineLatest, Observable, timer } from 'rxjs';
|
||||||
|
import { delayWhen, map, retryWhen, scan, skip, switchMap, tap } from 'rxjs/operators';
|
||||||
|
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
|
||||||
|
import { ApiService } from 'src/app/services/api.service';
|
||||||
|
import { StateService } from 'src/app/services/state.service';
|
||||||
|
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-blocks-list',
|
||||||
|
templateUrl: './blocks-list.component.html',
|
||||||
|
styleUrls: ['./blocks-list.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class BlocksList implements OnInit {
|
||||||
|
@Input() widget: boolean = false;
|
||||||
|
|
||||||
|
blocks$: Observable<any[]> = undefined;
|
||||||
|
|
||||||
|
isLoading = true;
|
||||||
|
fromBlockHeight = undefined;
|
||||||
|
paginationMaxSize: number;
|
||||||
|
page = 1;
|
||||||
|
lastPage = 1;
|
||||||
|
blocksCount: number;
|
||||||
|
fromHeightSubject: BehaviorSubject<number> = new BehaviorSubject(this.fromBlockHeight);
|
||||||
|
skeletonLines: number[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private apiService: ApiService,
|
||||||
|
private websocketService: WebsocketService,
|
||||||
|
public stateService: StateService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.websocketService.want(['blocks']);
|
||||||
|
|
||||||
|
this.skeletonLines = this.widget === true ? [...Array(5).keys()] : [...Array(15).keys()];
|
||||||
|
this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5;
|
||||||
|
|
||||||
|
this.blocks$ = combineLatest([
|
||||||
|
this.fromHeightSubject.pipe(
|
||||||
|
switchMap((fromBlockHeight) => {
|
||||||
|
this.isLoading = true;
|
||||||
|
return this.apiService.getBlocks$(this.page === 1 ? undefined : fromBlockHeight)
|
||||||
|
.pipe(
|
||||||
|
tap(blocks => {
|
||||||
|
if (this.blocksCount === undefined) {
|
||||||
|
this.blocksCount = blocks[0].height;
|
||||||
|
}
|
||||||
|
this.isLoading = false;
|
||||||
|
}),
|
||||||
|
map(blocks => {
|
||||||
|
for (const block of blocks) {
|
||||||
|
// @ts-ignore: Need to add an extra field for the template
|
||||||
|
block.extras.pool.logo = `./resources/mining-pools/` +
|
||||||
|
block.extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg';
|
||||||
|
}
|
||||||
|
if (this.widget) {
|
||||||
|
return blocks.slice(0, 5);
|
||||||
|
}
|
||||||
|
return blocks;
|
||||||
|
}),
|
||||||
|
retryWhen(errors => errors.pipe(delayWhen(() => timer(1000))))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
),
|
||||||
|
this.stateService.blocks$
|
||||||
|
.pipe(
|
||||||
|
skip(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT - 1),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.pipe(
|
||||||
|
scan((acc, blocks) => {
|
||||||
|
if (this.page > 1 || acc.length === 0 || (this.page === 1 && this.lastPage !== 1)) {
|
||||||
|
this.lastPage = this.page;
|
||||||
|
return blocks[0];
|
||||||
|
}
|
||||||
|
this.blocksCount = Math.max(this.blocksCount, blocks[1][0].height);
|
||||||
|
// @ts-ignore: Need to add an extra field for the template
|
||||||
|
blocks[1][0].extras.pool.logo = `./resources/mining-pools/` +
|
||||||
|
blocks[1][0].extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg';
|
||||||
|
acc.unshift(blocks[1][0]);
|
||||||
|
acc = acc.slice(0, this.widget ? 5 : 15);
|
||||||
|
return acc;
|
||||||
|
}, [])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pageChange(page: number) {
|
||||||
|
this.fromHeightSubject.next(this.blocksCount - (page - 1) * 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
trackByBlock(index: number, block: BlockExtended) {
|
||||||
|
return block.height;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<div>
|
<div style="min-height: 295px">
|
||||||
<table class="table latest-transactions" style="min-height: 295px">
|
<table class="table latest-transactions">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="d-none d-md-block" i18n="block.height">Height</th>
|
<th class="d-none d-md-block" i18n="block.height">Height</th>
|
||||||
@ -17,7 +17,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="text-right">{{ diffChange.difficultyShorten }}</td>
|
<td class="text-right">{{ diffChange.difficultyShorten }}</td>
|
||||||
<td class="text-right" [style]="diffChange.change >= 0 ? 'color: #42B747' : 'color: #B74242'">
|
<td class="text-right" [style]="diffChange.change >= 0 ? 'color: #42B747' : 'color: #B74242'">
|
||||||
{{ diffChange.change >= 0 ? '+' : '' }}{{ formatNumber(diffChange.change, locale, '1.2-2') }}%
|
{{ diffChange.change >= 0 ? '+' : '' }}{{ diffChange.change | amountShortener }}%
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -43,7 +43,7 @@ export class DifficultyAdjustmentsTable implements OnInit {
|
|||||||
const change = (data.difficulty[i].difficulty / data.difficulty[i - 1].difficulty - 1) * 100;
|
const change = (data.difficulty[i].difficulty / data.difficulty[i - 1].difficulty - 1) * 100;
|
||||||
|
|
||||||
tableData.push(Object.assign(data.difficulty[i], {
|
tableData.push(Object.assign(data.difficulty[i], {
|
||||||
change: change,
|
change: Math.round(change * 100) / 100,
|
||||||
difficultyShorten: formatNumber(
|
difficultyShorten: formatNumber(
|
||||||
data.difficulty[i].difficulty / selectedPowerOfTen.divider,
|
data.difficulty[i].difficulty / selectedPowerOfTen.divider,
|
||||||
this.locale, '1.2-2') + selectedPowerOfTen.unit
|
this.locale, '1.2-2') + selectedPowerOfTen.unit
|
||||||
|
@ -5,30 +5,32 @@
|
|||||||
<!-- Temporary stuff here - Will be moved to a component once we have more useful data to show -->
|
<!-- Temporary stuff here - Will be moved to a component once we have more useful data to show -->
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="main-title">Reward stats</div>
|
<div class="main-title">Reward stats</div>
|
||||||
<div class="card" style="height: 123px">
|
<div class="card-wrapper">
|
||||||
<div class="card-body more-padding">
|
<div class="card" style="height: 123px">
|
||||||
<div class="fee-estimation-container" *ngIf="$rewardStats | async as rewardStats; else loadingReward">
|
<div class="card-body more-padding">
|
||||||
<div class="item">
|
<div class="reward-container" *ngIf="$rewardStats | async as rewardStats; else loadingReward">
|
||||||
<h5 class="card-title" i18n="">Miners Reward</h5>
|
<div class="item">
|
||||||
<div class="card-text">
|
<h5 class="card-title" i18n="mining.rewards">Miners Reward</h5>
|
||||||
<app-amount [satoshis]="rewardStats.totalReward" digitsInfo="1.2-2" [noFiat]="true"></app-amount>
|
<div class="card-text">
|
||||||
<div class="symbol">in the last 8 blocks</div>
|
<app-amount [satoshis]="rewardStats.totalReward" digitsInfo="1.2-2" [noFiat]="true"></app-amount>
|
||||||
|
<div class="symbol">in the last 8 blocks</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="item">
|
||||||
<div class="item">
|
<h5 class="card-title" i18n="mining.rewards-per-tx">Reward Per Tx</h5>
|
||||||
<h5 class="card-title" i18n="">Reward Per Tx</h5>
|
<div class="card-text">
|
||||||
<div class="card-text">
|
{{ rewardStats.rewardPerTx | amountShortener }}
|
||||||
{{ rewardStats.rewardPerTx | amountShortener }}
|
<span class="symbol">sats/tx</span>
|
||||||
<span class="symbol">sats/tx</span>
|
<div class="symbol">in the last 8 blocks</div>
|
||||||
<div class="symbol">in the last 8 blocks</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="item">
|
||||||
<div class="item">
|
<h5 class="card-title" i18n="mining.average-fee">Average Fee</h5>
|
||||||
<h5 class="card-title" i18n="">Average Fee</h5>
|
<div class="card-text">
|
||||||
<div class="card-text">
|
{{ rewardStats.feePerTx | amountShortener}}
|
||||||
{{ rewardStats.feePerTx | amountShortener}}
|
<span class="symbol">sats/tx</span>
|
||||||
<span class="symbol">sats/tx</span>
|
<div class="symbol">in the last 8 blocks</div>
|
||||||
<div class="symbol">in the last 8 blocks</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -36,24 +38,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ng-template #loadingReward>
|
<ng-template #loadingReward>
|
||||||
<div class="fee-estimation-container">
|
<div class="reward-container">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h5 class="card-title" i18n="">Miners Reward</h5>
|
<h5 class="card-title" i18n="mining.rewards">Miners Reward</h5>
|
||||||
<div class="card-text">
|
<div class="card-text skeleton">
|
||||||
<div class="skeleton-loader"></div>
|
<div class="skeleton-loader"></div>
|
||||||
<div class="skeleton-loader"></div>
|
<div class="skeleton-loader"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h5 class="card-title" i18n="">Reward Per Tx</h5>
|
<h5 class="card-title" i18n="mining.rewards-per-tx">Reward Per Tx</h5>
|
||||||
<div class="card-text">
|
<div class="card-text skeleton">
|
||||||
<div class="skeleton-loader"></div>
|
<div class="skeleton-loader"></div>
|
||||||
<div class="skeleton-loader"></div>
|
<div class="skeleton-loader"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h5 class="card-title" i18n="">Average Fee</h5>
|
<h5 class="card-title" i18n="mining.average-fee">Average Fee</h5>
|
||||||
<div class="card-text">
|
<div class="card-text skeleton">
|
||||||
<div class="skeleton-loader"></div>
|
<div class="skeleton-loader"></div>
|
||||||
<div class="skeleton-loader"></div>
|
<div class="skeleton-loader"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -95,7 +97,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- pool dominance -->
|
<!-- pool dominance -->
|
||||||
<div class="col">
|
<!-- <div class="col">
|
||||||
<div class="card" style="height: 385px">
|
<div class="card" style="height: 385px">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">
|
<h5 class="card-title">
|
||||||
@ -106,6 +108,20 @@
|
|||||||
more »</a></div>
|
more »</a></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<!-- Latest blocks -->
|
||||||
|
<div class="col">
|
||||||
|
<div class="card" style="height: 385px">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">
|
||||||
|
Latest blocks
|
||||||
|
</h5>
|
||||||
|
<app-blocks-list [widget]=true></app-blocks-list>
|
||||||
|
<div><a [routerLink]="['/mining/blocks' | relativeUrl]" i18n="dashboard.view-more">View
|
||||||
|
more »</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col">
|
<div class="col">
|
||||||
@ -115,7 +131,7 @@
|
|||||||
Adjustments
|
Adjustments
|
||||||
</h5>
|
</h5>
|
||||||
<app-difficulty-adjustments-table></app-difficulty-adjustments-table>
|
<app-difficulty-adjustments-table></app-difficulty-adjustments-table>
|
||||||
<div class="mt-1"><a [routerLink]="['/mining/hashrate' | relativeUrl]" i18n="dashboard.view-more">View more
|
<div><a [routerLink]="['/mining/hashrate' | relativeUrl]" i18n="dashboard.view-more">View more
|
||||||
»</a></div>
|
»</a></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,6 +28,9 @@
|
|||||||
.card-body.pool-ranking {
|
.card-body.pool-ranking {
|
||||||
padding: 1.25rem 0.25rem 0.75rem 0.25rem;
|
padding: 1.25rem 0.25rem 0.75rem 0.25rem;
|
||||||
}
|
}
|
||||||
|
.card-text {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
#blockchain-container {
|
#blockchain-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -56,50 +59,57 @@
|
|||||||
padding-bottom: 3px;
|
padding-bottom: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fee-estimation-container {
|
.reward-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
flex-direction: row;
|
||||||
@media (min-width: 376px) {
|
justify-content: space-around;
|
||||||
flex-direction: row;
|
height: 76px;
|
||||||
|
.shared-block {
|
||||||
|
color: #ffffff66;
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
.item {
|
.item {
|
||||||
max-width: 150px;
|
display: table-cell;
|
||||||
margin: 0;
|
padding: 0 5px;
|
||||||
width: -webkit-fill-available;
|
width: 100%;
|
||||||
@media (min-width: 376px) {
|
&:nth-child(1) {
|
||||||
margin: 0 auto 0px;
|
|
||||||
}
|
|
||||||
&:first-child{
|
|
||||||
display: none;
|
display: none;
|
||||||
@media (min-width: 485px) {
|
@media (min-width: 485px) {
|
||||||
display: block;
|
display: table-cell;
|
||||||
}
|
}
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@media (min-width: 992px) {
|
@media (min-width: 992px) {
|
||||||
display: block;
|
display: table-cell;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:last-child {
|
}
|
||||||
margin-bottom: 0;
|
.card-text {
|
||||||
}
|
font-size: 22px;
|
||||||
.card-text span {
|
margin-top: -9px;
|
||||||
color: #ffffff66;
|
position: relative;
|
||||||
font-size: 12px;
|
}
|
||||||
top: 0px;
|
.card-text.skeleton {
|
||||||
}
|
margin-top: 0px;
|
||||||
.fee-text{
|
}
|
||||||
border-bottom: 1px solid #ffffff1c;
|
}
|
||||||
width: fit-content;
|
|
||||||
margin: auto;
|
.more-padding {
|
||||||
line-height: 1.45;
|
padding: 18px;
|
||||||
padding: 0px 2px;
|
}
|
||||||
}
|
|
||||||
.fiat {
|
.card-wrapper {
|
||||||
display: block;
|
.card {
|
||||||
font-size: 14px !important;
|
height: auto !important;
|
||||||
}
|
}
|
||||||
|
.card-body {
|
||||||
|
display: flex;
|
||||||
|
flex: inherit;
|
||||||
|
text-align: center;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
padding: 22px 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,8 +36,8 @@ export class MiningDashboardComponent implements OnInit {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'totalReward': totalReward,
|
'totalReward': totalReward,
|
||||||
'rewardPerTx': totalReward / totalTx,
|
'rewardPerTx': Math.round(totalReward / totalTx),
|
||||||
'feePerTx': totalFee / totalTx,
|
'feePerTx': Math.round(totalFee / totalTx),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -21,7 +21,10 @@
|
|||||||
<table class="table table-borderless smaller-text table-sm" id="table-tx-vin">
|
<table class="table table-borderless smaller-text table-sm" id="table-tx-vin">
|
||||||
<tbody>
|
<tbody>
|
||||||
<ng-template ngFor let-vin [ngForOf]="tx['@vinLimit'] ? ((tx.vin.length>12)?tx.vin.slice(0, 10): tx.vin.slice(0, 12)) : tx.vin" [ngForTrackBy]="trackByIndexFn">
|
<ng-template ngFor let-vin [ngForOf]="tx['@vinLimit'] ? ((tx.vin.length>12)?tx.vin.slice(0, 10): tx.vin.slice(0, 12)) : tx.vin" [ngForTrackBy]="trackByIndexFn">
|
||||||
<tr [ngClass]="assetsMinimal && vin.prevout && assetsMinimal[vin.prevout.asset] && !vin.is_coinbase && vin.prevout.scriptpubkey_address && tx._unblinded ? 'assetBox' : ''">
|
<tr [ngClass]="{
|
||||||
|
'assetBox': assetsMinimal && vin.prevout && assetsMinimal[vin.prevout.asset] && !vin.is_coinbase && vin.prevout.scriptpubkey_address && tx._unblinded,
|
||||||
|
'highlight': vin.prevout?.scriptpubkey_address === this.address && this.address !== ''
|
||||||
|
}">
|
||||||
<td class="arrow-td">
|
<td class="arrow-td">
|
||||||
<ng-template [ngIf]="vin.prevout === null && !vin.is_pegin" [ngIfElse]="hasPrevout">
|
<ng-template [ngIf]="vin.prevout === null && !vin.is_pegin" [ngIfElse]="hasPrevout">
|
||||||
<span class="grey">
|
<span class="grey">
|
||||||
@ -143,7 +146,10 @@
|
|||||||
<table class="table table-borderless smaller-text table-sm" id="table-tx-vout">
|
<table class="table table-borderless smaller-text table-sm" id="table-tx-vout">
|
||||||
<tbody>
|
<tbody>
|
||||||
<ng-template ngFor let-vout let-vindex="index" [ngForOf]="tx['@voutLimit'] && !outputIndex ? ((tx.vout.length > 12) ? tx.vout.slice(0, 10) : tx.vout.slice(0, 12)) : tx.vout" [ngForTrackBy]="trackByIndexFn">
|
<ng-template ngFor let-vout let-vindex="index" [ngForOf]="tx['@voutLimit'] && !outputIndex ? ((tx.vout.length > 12) ? tx.vout.slice(0, 10) : tx.vout.slice(0, 12)) : tx.vout" [ngForTrackBy]="trackByIndexFn">
|
||||||
<tr [ngClass]="assetsMinimal && assetsMinimal[vout.asset] && vout.scriptpubkey_address && tx.vin && !tx.vin[0].is_coinbase && tx._unblinded || outputIndex === vindex ? 'assetBox' : ''">
|
<tr [ngClass]="{
|
||||||
|
'assetBox': assetsMinimal && assetsMinimal[vout.asset] && vout.scriptpubkey_address && tx.vin && !tx.vin[0].is_coinbase && tx._unblinded || outputIndex === vindex,
|
||||||
|
'highlight': vout.scriptpubkey_address === this.address && this.address !== ''
|
||||||
|
}">
|
||||||
<td>
|
<td>
|
||||||
<a *ngIf="vout.scriptpubkey_address; else scriptpubkey_type" [routerLink]="['/address/' | relativeUrl, vout.scriptpubkey_address]" title="{{ vout.scriptpubkey_address }}">
|
<a *ngIf="vout.scriptpubkey_address; else scriptpubkey_type" [routerLink]="['/address/' | relativeUrl, vout.scriptpubkey_address]" title="{{ vout.scriptpubkey_address }}">
|
||||||
<span class="d-block d-lg-none">{{ vout.scriptpubkey_address | shortenString : 16 }}</span>
|
<span class="d-block d-lg-none">{{ vout.scriptpubkey_address | shortenString : 16 }}</span>
|
||||||
@ -242,13 +248,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="summary">
|
||||||
<div class="float-left mt-2-5" *ngIf="!transactionPage && !tx.vin[0].is_coinbase">
|
<div class="float-left mt-2-5" *ngIf="!transactionPage && !tx.vin[0].is_coinbase">
|
||||||
{{ tx.fee / (tx.weight / 4) | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="d-none d-sm-inline-block"> – {{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [value]="tx.fee"></app-fiat></span></span>
|
{{ tx.fee / (tx.weight / 4) | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="d-none d-sm-inline-block"> – {{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> <span class="fiat"><app-fiat [value]="tx.fee"></app-fiat></span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="float-right">
|
<div class="float-right">
|
||||||
<span *ngIf="showConfirmations && latestBlock$ | async as latestBlock">
|
<ng-container *ngIf="showConfirmations && latestBlock$ | async as latestBlock">
|
||||||
<button *ngIf="tx.status.confirmed; else unconfirmedButton" type="button" class="btn btn-sm btn-success mt-2">
|
<button *ngIf="tx.status.confirmed; else unconfirmedButton" type="button" class="btn btn-sm btn-success mt-2">
|
||||||
<ng-container *ngTemplateOutlet="latestBlock.height - tx.status.block_height + 1 == 1 ? confirmationSingular : confirmationPlural; context: {$implicit: latestBlock.height - tx.status.block_height + 1}"></ng-container>
|
<ng-container *ngTemplateOutlet="latestBlock.height - tx.status.block_height + 1 == 1 ? confirmationSingular : confirmationPlural; context: {$implicit: latestBlock.height - tx.status.block_height + 1}"></ng-container>
|
||||||
<ng-template #confirmationSingular let-i i18n="shared.confirmation-count.singular|Transaction singular confirmation count">{{ i }} confirmation</ng-template>
|
<ng-template #confirmationSingular let-i i18n="shared.confirmation-count.singular|Transaction singular confirmation count">{{ i }} confirmation</ng-template>
|
||||||
@ -257,14 +263,18 @@
|
|||||||
<ng-template #unconfirmedButton>
|
<ng-template #unconfirmedButton>
|
||||||
<button type="button" class="btn btn-sm btn-danger mt-2" i18n="transaction.unconfirmed|Transaction unconfirmed state">Unconfirmed</button>
|
<button type="button" class="btn btn-sm btn-danger mt-2" i18n="transaction.unconfirmed|Transaction unconfirmed state">Unconfirmed</button>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
</ng-container>
|
||||||
</span>
|
<button *ngIf="address === ''; else viewingAddress" type="button" class="btn btn-sm btn-primary mt-2 ml-2" (click)="switchCurrency()">
|
||||||
<button type="button" class="btn btn-sm btn-primary mt-2" (click)="switchCurrency()">
|
|
||||||
<ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && haveBlindedOutputValues(tx)" [ngIfElse]="defaultAmount" i18n="shared.confidential">Confidential</ng-template>
|
<ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && haveBlindedOutputValues(tx)" [ngIfElse]="defaultAmount" i18n="shared.confidential">Confidential</ng-template>
|
||||||
<ng-template #defaultAmount>
|
<ng-template #defaultAmount>
|
||||||
<app-amount [satoshis]="getTotalTxOutput(tx)"></app-amount>
|
<app-amount [satoshis]="getTotalTxOutput(tx)"></app-amount>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</button>
|
</button>
|
||||||
|
<ng-template #viewingAddress>
|
||||||
|
<button type="button" class="btn btn-sm mt-2 ml-2" (click)="switchCurrency()" [ngClass]="{'btn-success': tx['addressValue'] >= 0, 'btn-danger': tx['addressValue'] < 0}">
|
||||||
|
<app-amount [satoshis]="tx['addressValue']" [addPlus]="true"></app-amount>
|
||||||
|
</button>
|
||||||
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
|
||||||
.arrow-td {
|
.arrow-td {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
.green, .grey, .red {
|
.green, .grey, .red {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
top: -2px;
|
top: 1px;
|
||||||
position: relative;
|
position: relative;
|
||||||
@media( min-width: 576px){
|
@media( min-width: 576px){
|
||||||
font-size: 19px;
|
font-size: 19px;
|
||||||
@ -119,3 +121,11 @@
|
|||||||
h2 {
|
h2 {
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
background-color: #181b2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
|
import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { Observable, forkJoin, ReplaySubject, BehaviorSubject, merge, of, Subject, Subscription } from 'rxjs';
|
import { Observable, forkJoin, ReplaySubject, BehaviorSubject, merge, Subscription } from 'rxjs';
|
||||||
import { Outspend, Transaction } from '../../interfaces/electrs.interface';
|
import { Outspend, Transaction, Vin, Vout } from '../../interfaces/electrs.interface';
|
||||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||||
import { environment } from 'src/environments/environment';
|
import { environment } from 'src/environments/environment';
|
||||||
import { AssetsService } from 'src/app/services/assets.service';
|
import { AssetsService } from 'src/app/services/assets.service';
|
||||||
import { map, share, switchMap, tap } from 'rxjs/operators';
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
|
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -23,6 +23,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
@Input() transactionPage = false;
|
@Input() transactionPage = false;
|
||||||
@Input() errorUnblinded = false;
|
@Input() errorUnblinded = false;
|
||||||
@Input() outputIndex: number;
|
@Input() outputIndex: number;
|
||||||
|
@Input() address: string = '';
|
||||||
|
|
||||||
@Output() loadMore = new EventEmitter();
|
@Output() loadMore = new EventEmitter();
|
||||||
|
|
||||||
@ -98,6 +99,21 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
if (this.outspends[i]) {
|
if (this.outspends[i]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.address) {
|
||||||
|
const addressIn = tx.vout
|
||||||
|
.filter((v: Vout) => v.scriptpubkey_address === this.address)
|
||||||
|
.map((v: Vout) => v.value || 0)
|
||||||
|
.reduce((a: number, b: number) => a + b, 0);
|
||||||
|
|
||||||
|
const addressOut = tx.vin
|
||||||
|
.filter((v: Vin) => v.prevout && v.prevout.scriptpubkey_address === this.address)
|
||||||
|
.map((v: Vin) => v.prevout.value || 0)
|
||||||
|
.reduce((a: number, b: number) => a + b, 0);
|
||||||
|
|
||||||
|
tx['addressValue'] = addressIn || -addressOut;
|
||||||
|
}
|
||||||
|
|
||||||
observableObject[i] = this.electrsApiService.getOutspends$(tx.txid);
|
observableObject[i] = this.electrsApiService.getOutspends$(tx.txid);
|
||||||
});
|
});
|
||||||
this.refreshOutspends$.next(observableObject);
|
this.refreshOutspends$.next(observableObject);
|
||||||
@ -119,7 +135,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTotalTxOutput(tx: Transaction) {
|
getTotalTxOutput(tx: Transaction) {
|
||||||
return tx.vout.map((v: any) => v.value || 0).reduce((a: number, b: number) => a + b);
|
return tx.vout.map((v: Vout) => v.value || 0).reduce((a: number, b: number) => a + b);
|
||||||
}
|
}
|
||||||
|
|
||||||
switchCurrency() {
|
switchCurrency() {
|
||||||
|
@ -72,7 +72,7 @@ export interface Vout {
|
|||||||
scriptpubkey: string;
|
scriptpubkey: string;
|
||||||
scriptpubkey_asm: string;
|
scriptpubkey_asm: string;
|
||||||
scriptpubkey_type: string;
|
scriptpubkey_type: string;
|
||||||
scriptpubkey_address: string;
|
scriptpubkey_address?: string;
|
||||||
value: number;
|
value: number;
|
||||||
// Elements
|
// Elements
|
||||||
valuecommitment?: number;
|
valuecommitment?: number;
|
||||||
|
@ -151,6 +151,13 @@ export class ApiService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBlocks$(from: number): Observable<BlockExtended[]> {
|
||||||
|
return this.httpClient.get<BlockExtended[]>(
|
||||||
|
this.apiBaseUrl + this.apiBasePath + `/api/v1/blocks-extras` +
|
||||||
|
(from !== undefined ? `/${from}` : ``)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getHistoricalDifficulty$(interval: string | undefined): Observable<any> {
|
getHistoricalDifficulty$(interval: string | undefined): Observable<any> {
|
||||||
return this.httpClient.get<any[]>(
|
return this.httpClient.get<any[]>(
|
||||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/difficulty` +
|
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/difficulty` +
|
||||||
|
@ -1,22 +1,39 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core';
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
// https://medium.com/@thunderroid/angular-short-number-suffix-pipe-1k-2m-3b-dded4af82fb4
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'amountShortener'
|
name: 'amountShortener'
|
||||||
})
|
})
|
||||||
export class AmountShortenerPipe implements PipeTransform {
|
export class AmountShortenerPipe implements PipeTransform {
|
||||||
transform(num: number, ...args: number[]): unknown {
|
transform(number: number, args?: any): any {
|
||||||
const digits = args[0] || 1;
|
if (isNaN(number)) return null; // will only work value is a number
|
||||||
const lookup = [
|
if (number === null) return null;
|
||||||
{ value: 1, symbol: '' },
|
if (number === 0) return null;
|
||||||
{ value: 1e3, symbol: 'k' },
|
let abs = Math.abs(number);
|
||||||
{ value: 1e6, symbol: 'M' },
|
const rounder = Math.pow(10, 1);
|
||||||
{ value: 1e9, symbol: 'G' },
|
const isNegative = number < 0; // will also work for Negetive numbers
|
||||||
{ value: 1e12, symbol: 'T' },
|
let key = '';
|
||||||
{ value: 1e15, symbol: 'P' },
|
|
||||||
{ value: 1e18, symbol: 'E' }
|
const powers = [
|
||||||
|
{ key: 'E', value: 10e18 },
|
||||||
|
{ key: 'P', value: 10e15 },
|
||||||
|
{ key: 'T', value: 10e12 },
|
||||||
|
{ key: 'B', value: 10e9 },
|
||||||
|
{ key: 'M', value: 10e6 },
|
||||||
|
{ key: 'K', value: 1000 }
|
||||||
];
|
];
|
||||||
const rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
|
|
||||||
var item = lookup.slice().reverse().find((item) => num >= item.value);
|
for (let i = 0; i < powers.length; i++) {
|
||||||
return item ? (num / item.value).toFixed(digits).replace(rx, '$1') + item.symbol : '0';
|
let reduced = abs / powers[i].value;
|
||||||
|
reduced = Math.round(reduced * rounder) / rounder;
|
||||||
|
if (reduced >= 1) {
|
||||||
|
abs = reduced;
|
||||||
|
key = powers[i].key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (isNegative ? '-' : '') + abs + key;
|
||||||
}
|
}
|
||||||
}
|
}
|
BIN
frontend/src/resources/profile/start9.png
Normal file
BIN
frontend/src/resources/profile/start9.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@ -269,7 +269,7 @@ ELEMENTS_ELECTRS_HOME=${ELEMENTS_HOME}/electrs
|
|||||||
|
|
||||||
MEMPOOL_REPO_URL=https://github.com/mempool/mempool
|
MEMPOOL_REPO_URL=https://github.com/mempool/mempool
|
||||||
MEMPOOL_REPO_NAME=mempool
|
MEMPOOL_REPO_NAME=mempool
|
||||||
MEMPOOL_REPO_BRANCH=wiz/installer2
|
MEMPOOL_REPO_BRANCH=master
|
||||||
MEMPOOL_LATEST_RELEASE=v2.3.1
|
MEMPOOL_LATEST_RELEASE=v2.3.1
|
||||||
|
|
||||||
BITCOIN_REPO_URL=https://github.com/bitcoin/bitcoin
|
BITCOIN_REPO_URL=https://github.com/bitcoin/bitcoin
|
||||||
@ -326,7 +326,7 @@ FREEBSD_PKG=()
|
|||||||
FREEBSD_PKG+=(zsh sudo git screen curl wget calc neovim)
|
FREEBSD_PKG+=(zsh sudo git screen curl wget calc neovim)
|
||||||
FREEBSD_PKG+=(openssh-portable py38-pip rust llvm90)
|
FREEBSD_PKG+=(openssh-portable py38-pip rust llvm90)
|
||||||
FREEBSD_PKG+=(boost-libs autoconf automake gmake gcc libevent libtool pkgconf)
|
FREEBSD_PKG+=(boost-libs autoconf automake gmake gcc libevent libtool pkgconf)
|
||||||
FREEBSD_PKG+=(nginx rsync py38-certbot-nginx )
|
FREEBSD_PKG+=(nginx rsync py38-certbot-nginx mariadb105-server)
|
||||||
|
|
||||||
#############################
|
#############################
|
||||||
##### utility functions #####
|
##### utility functions #####
|
||||||
@ -819,8 +819,13 @@ osSudo "${ROOT_USER}" chown -R "${MEMPOOL_USER}:${MEMPOOL_GROUP}" "${MEMPOOL_HOM
|
|||||||
osSudo "${MEMPOOL_USER}" touch "${MEMPOOL_HOME}/.zshrc"
|
osSudo "${MEMPOOL_USER}" touch "${MEMPOOL_HOME}/.zshrc"
|
||||||
|
|
||||||
echo "[*] Cloning Mempool repo from ${MEMPOOL_REPO_URL}"
|
echo "[*] Cloning Mempool repo from ${MEMPOOL_REPO_URL}"
|
||||||
|
osSudo "${MEMPOOL_USER}" git config --global pull.rebase true
|
||||||
osSudo "${MEMPOOL_USER}" git config --global advice.detachedHead false
|
osSudo "${MEMPOOL_USER}" git config --global advice.detachedHead false
|
||||||
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}"
|
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}"
|
||||||
|
osSudo "${MEMPOOL_USER}" ln -s mempool/production/mempool-build-all upgrade
|
||||||
|
osSudo "${MEMPOOL_USER}" ln -s mempool/production/mempool-kill-all stop
|
||||||
|
osSudo "${MEMPOOL_USER}" ln -s mempool/production/mempool-start-all start
|
||||||
|
osSudo "${MEMPOOL_USER}" ln -s mempool/production/mempool-restart-all restart
|
||||||
|
|
||||||
echo "[*] Installing nvm.sh from GitHub"
|
echo "[*] Installing nvm.sh from GitHub"
|
||||||
osSudo "${MEMPOOL_USER}" sh -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | zsh'
|
osSudo "${MEMPOOL_USER}" sh -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | zsh'
|
||||||
@ -1256,6 +1261,48 @@ if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
|
|||||||
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME} && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME} && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "${BISQ_ENABLE}" = ON ];then
|
||||||
|
echo "[*] Creating Mempool instance for Bisq"
|
||||||
|
osSudo "${MEMPOOL_USER}" git config --global advice.detachedHead false
|
||||||
|
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/bisq"
|
||||||
|
|
||||||
|
echo "[*] Checking out Mempool ${MEMPOOL_LATEST_RELEASE} for Bisq"
|
||||||
|
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME} && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
##### mariadb
|
||||||
|
|
||||||
|
echo "[*] Adding MySQL configuration"
|
||||||
|
case $OS in
|
||||||
|
|
||||||
|
FreeBSD)
|
||||||
|
osSudo "${ROOT_USER}" service mysql-server start
|
||||||
|
;;
|
||||||
|
Debian)
|
||||||
|
osSudo "${ROOT_USER}" service mysql start
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
mysql << _EOF_
|
||||||
|
create database mempool;
|
||||||
|
grant all on mempool.* to 'mempool'@'localhost' identified by 'mempool';
|
||||||
|
|
||||||
|
create database mempool_testnet;
|
||||||
|
grant all on mempool_testnet.* to 'mempool_testnet'@'localhost' identified by 'mempool_testnet';
|
||||||
|
|
||||||
|
create database mempool_signet;
|
||||||
|
grant all on mempool_signet.* to 'mempool_signet'@'localhost' identified by 'mempool_signet';
|
||||||
|
|
||||||
|
create database mempool_liquid;
|
||||||
|
grant all on mempool_liquid.* to 'mempool_liquid'@'localhost' identified by 'mempool_liquid';
|
||||||
|
|
||||||
|
create database mempool_liquidtestnet;
|
||||||
|
grant all on mempool_liquidtestnet.* to 'mempool_liquidtestnet'@'localhost' identified by 'mempool_liquidtestnet';
|
||||||
|
|
||||||
|
create database mempool_bisq;
|
||||||
|
grant all on mempool_bisq.* to 'mempool_bisq'@'localhost' identified by 'mempool_bisq';
|
||||||
|
_EOF_
|
||||||
|
|
||||||
##### nginx
|
##### nginx
|
||||||
|
|
||||||
echo "[*] Adding Nginx configuration"
|
echo "[*] Adding Nginx configuration"
|
||||||
|
@ -4,6 +4,12 @@ HOSTNAME=$(hostname)
|
|||||||
LOCKFILE="${HOME}/lock"
|
LOCKFILE="${HOME}/lock"
|
||||||
REF=$(echo "${1:=origin/master}"|sed -e 's!:!/!')
|
REF=$(echo "${1:=origin/master}"|sed -e 's!:!/!')
|
||||||
|
|
||||||
|
# get rpc credentials
|
||||||
|
BITCOIN_RPC_USER=$(grep '^rpcuser' /bitcoin/bitcoin.conf | cut -d '=' -f2)
|
||||||
|
BITCOIN_RPC_PASS=$(grep '^rpcpassword' /bitcoin/bitcoin.conf | cut -d '=' -f2)
|
||||||
|
ELEMENTS_RPC_USER=$(grep '^rpcuser' /elements/elements.conf | cut -d '=' -f2)
|
||||||
|
ELEMENTS_RPC_PASS=$(grep '^rpcpassword' /elements/elements.conf | cut -d '=' -f2)
|
||||||
|
|
||||||
if [ -f "${LOCKFILE}" ];then
|
if [ -f "${LOCKFILE}" ];then
|
||||||
echo "upgrade already running? check lockfile ${LOCKFILE}"
|
echo "upgrade already running? check lockfile ${LOCKFILE}"
|
||||||
exit 1
|
exit 1
|
||||||
@ -42,6 +48,9 @@ build_frontend()
|
|||||||
echo "[*] Building frontend for ${site}"
|
echo "[*] Building frontend for ${site}"
|
||||||
[ -z "${HASH}" ] && exit 1
|
[ -z "${HASH}" ] && exit 1
|
||||||
cd "$HOME/${site}/frontend" || exit 1
|
cd "$HOME/${site}/frontend" || exit 1
|
||||||
|
if [ ! -e "mempool-frontend-config.json" ];then
|
||||||
|
cp "${HOME}/mempool/production/mempool-frontend-config.${site}.json" "mempool-frontend-config.json"
|
||||||
|
fi
|
||||||
npm install --no-optional || exit 1
|
npm install --no-optional || exit 1
|
||||||
npm run build || exit 1
|
npm run build || exit 1
|
||||||
}
|
}
|
||||||
@ -52,6 +61,15 @@ build_backend()
|
|||||||
echo "[*] Building backend for ${site}"
|
echo "[*] Building backend for ${site}"
|
||||||
[ -z "${HASH}" ] && exit 1
|
[ -z "${HASH}" ] && exit 1
|
||||||
cd "$HOME/${site}/backend" || exit 1
|
cd "$HOME/${site}/backend" || exit 1
|
||||||
|
if [ ! -e "mempool-config.json" ];then
|
||||||
|
cp "${HOME}/mempool/production/mempool-config.${site}.json" "mempool-config.json"
|
||||||
|
sed -i .orig \
|
||||||
|
-e "s!__BITCOIN_RPC_USER__!${BITCOIN_RPC_USER}!" \
|
||||||
|
-e "s!__BITCOIN_RPC_PASS__!${BITCOIN_RPC_PASS}!" \
|
||||||
|
-e "s!__ELEMENTS_RPC_USER__!${ELEMENTS_RPC_USER}!" \
|
||||||
|
-e "s!__ELEMENTS_RPC_PASS__!${ELEMENTS_RPC_PASS}!" \
|
||||||
|
"mempool-config.json"
|
||||||
|
fi
|
||||||
npm install --no-optional || exit 1
|
npm install --no-optional || exit 1
|
||||||
npm run build || exit 1
|
npm run build || exit 1
|
||||||
}
|
}
|
||||||
@ -60,16 +78,28 @@ ship_frontend()
|
|||||||
{
|
{
|
||||||
local site="$1"
|
local site="$1"
|
||||||
cd "$HOME/${site}/frontend" || exit 1
|
cd "$HOME/${site}/frontend" || exit 1
|
||||||
rsync -av ./dist/mempool/browser/* "${HOME}/public_html/${site}/" || exit 1
|
mkdir -p "${HOME}/public_html/${site}/"
|
||||||
|
rsync -av "./dist/mempool/browser/" "${HOME}/public_html/${site}/" || exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
export NVM_DIR="${HOME}/.nvm"
|
export NVM_DIR="${HOME}/.nvm"
|
||||||
source "${NVM_DIR}/nvm.sh"
|
source "${NVM_DIR}/nvm.sh"
|
||||||
|
|
||||||
for target in mainnet testnet signet liquid liquidtestnet bisq;do update_repo "${target}";done
|
for target in mainnet testnet signet liquid liquidtestnet bisq;do
|
||||||
for target in mainnet testnet signet liquid liquidtestnet bisq;do build_backend "${target}";done
|
update_repo "${target}"
|
||||||
for target in mainnet liquid bisq;do build_frontend "${target}";done
|
done
|
||||||
for target in mainnet liquid bisq;do ship_frontend "${target}";done
|
|
||||||
|
for target in mainnet testnet signet liquid liquidtestnet bisq;do
|
||||||
|
build_backend "${target}"
|
||||||
|
done
|
||||||
|
|
||||||
|
for target in mainnet liquid bisq;do
|
||||||
|
build_frontend "${target}"
|
||||||
|
done
|
||||||
|
|
||||||
|
for target in mainnet liquid bisq;do
|
||||||
|
ship_frontend "${target}"
|
||||||
|
done
|
||||||
|
|
||||||
echo "${HOSTNAME} updated to \`${REF}\` @ \`${HASH}\`" | /usr/local/bin/keybase chat send --nonblock --channel general mempool.dev
|
echo "${HOSTNAME} updated to \`${REF}\` @ \`${HASH}\`" | /usr/local/bin/keybase chat send --nonblock --channel general mempool.dev
|
||||||
echo "${HOSTNAME} updated to \`${REF}\` @ \`${HASH}\`" | /usr/local/bin/keybase chat send --nonblock --channel general mempool.ops
|
echo "${HOSTNAME} updated to \`${REF}\` @ \`${HASH}\`" | /usr/local/bin/keybase chat send --nonblock --channel general mempool.ops
|
@ -12,8 +12,8 @@
|
|||||||
"MIN_PRIORITY": "debug"
|
"MIN_PRIORITY": "debug"
|
||||||
},
|
},
|
||||||
"CORE_RPC": {
|
"CORE_RPC": {
|
||||||
"USERNAME": "foo",
|
"USERNAME": "__BITCOIN_RPC_USER__",
|
||||||
"PASSWORD": "bar"
|
"PASSWORD": "__BITCOIN_RPC_PASS__"
|
||||||
},
|
},
|
||||||
"ESPLORA": {
|
"ESPLORA": {
|
||||||
"REST_API_URL": "http://127.0.0.1:4000"
|
"REST_API_URL": "http://127.0.0.1:4000"
|
||||||
@ -22,9 +22,9 @@
|
|||||||
"ENABLED": false,
|
"ENABLED": false,
|
||||||
"HOST": "127.0.0.1",
|
"HOST": "127.0.0.1",
|
||||||
"PORT": 3306,
|
"PORT": 3306,
|
||||||
"USERNAME": "bmempool",
|
"USERNAME": "mempool_bisq",
|
||||||
"PASSWORD": "bmempool",
|
"PASSWORD": "mempool_bisq",
|
||||||
"DATABASE": "bmempool"
|
"DATABASE": "mempool_bisq"
|
||||||
},
|
},
|
||||||
"STATISTICS": {
|
"STATISTICS": {
|
||||||
"ENABLED": true,
|
"ENABLED": true,
|
||||||
|
@ -13,13 +13,13 @@
|
|||||||
},
|
},
|
||||||
"CORE_RPC": {
|
"CORE_RPC": {
|
||||||
"PORT": 7041,
|
"PORT": 7041,
|
||||||
"USERNAME": "foo",
|
"USERNAME": "__ELEMENTS_RPC_USER__",
|
||||||
"PASSWORD": "bar"
|
"PASSWORD": "__ELEMENTS_RPC_PASS__"
|
||||||
},
|
},
|
||||||
"SECOND_CORE_RPC": {
|
"SECOND_CORE_RPC": {
|
||||||
"PORT": 8332,
|
"PORT": 8332,
|
||||||
"USERNAME": "foo",
|
"USERNAME": "__BITCOIN_RPC_USER__",
|
||||||
"PASSWORD": "bar"
|
"PASSWORD": "__BITCOIN_RPC_PASS__"
|
||||||
},
|
},
|
||||||
"ESPLORA": {
|
"ESPLORA": {
|
||||||
"REST_API_URL": "http://127.0.0.1:4001"
|
"REST_API_URL": "http://127.0.0.1:4001"
|
||||||
@ -28,9 +28,9 @@
|
|||||||
"ENABLED": true,
|
"ENABLED": true,
|
||||||
"HOST": "127.0.0.1",
|
"HOST": "127.0.0.1",
|
||||||
"PORT": 3306,
|
"PORT": 3306,
|
||||||
"USERNAME": "lmempool",
|
"USERNAME": "mempool_liquid",
|
||||||
"PASSWORD": "lmempool",
|
"PASSWORD": "mempool_liquid",
|
||||||
"DATABASE": "lmempool"
|
"DATABASE": "mempool_liquid"
|
||||||
},
|
},
|
||||||
"STATISTICS": {
|
"STATISTICS": {
|
||||||
"ENABLED": true,
|
"ENABLED": true,
|
||||||
|
@ -13,13 +13,13 @@
|
|||||||
},
|
},
|
||||||
"CORE_RPC": {
|
"CORE_RPC": {
|
||||||
"PORT": 7040,
|
"PORT": 7040,
|
||||||
"USERNAME": "foo",
|
"USERNAME": "__ELEMENTS_RPC_USER__",
|
||||||
"PASSWORD": "bar"
|
"PASSWORD": "__ELEMENTS_RPC_PASS__"
|
||||||
},
|
},
|
||||||
"SECOND_CORE_RPC": {
|
"SECOND_CORE_RPC": {
|
||||||
"PORT": 8332,
|
"PORT": 8332,
|
||||||
"USERNAME": "foo",
|
"USERNAME": "__BITCOIN_RPC_USER__",
|
||||||
"PASSWORD": "bar"
|
"PASSWORD": "__BITCOIN_RPC_PASS__"
|
||||||
},
|
},
|
||||||
"ESPLORA": {
|
"ESPLORA": {
|
||||||
"REST_API_URL": "http://127.0.0.1:4004"
|
"REST_API_URL": "http://127.0.0.1:4004"
|
||||||
|
@ -15,13 +15,13 @@
|
|||||||
},
|
},
|
||||||
"CORE_RPC": {
|
"CORE_RPC": {
|
||||||
"PORT": 8332,
|
"PORT": 8332,
|
||||||
"USERNAME": "foo",
|
"USERNAME": "__BITCOIN_RPC_USER__",
|
||||||
"PASSWORD": "bar"
|
"PASSWORD": "__BITCOIN_RPC_PASS__"
|
||||||
},
|
},
|
||||||
"SECOND_CORE_RPC": {
|
"SECOND_CORE_RPC": {
|
||||||
"PORT": 8302,
|
"PORT": 8302,
|
||||||
"USERNAME": "foo",
|
"USERNAME": "__BITCOIN_RPC_USER__",
|
||||||
"PASSWORD": "bar"
|
"PASSWORD": "__BITCOIN_RPC_PASS__"
|
||||||
},
|
},
|
||||||
"ESPLORA": {
|
"ESPLORA": {
|
||||||
"REST_API_URL": "http://127.0.0.1:4000"
|
"REST_API_URL": "http://127.0.0.1:4000"
|
||||||
|
@ -13,8 +13,8 @@
|
|||||||
},
|
},
|
||||||
"CORE_RPC": {
|
"CORE_RPC": {
|
||||||
"PORT": 38332,
|
"PORT": 38332,
|
||||||
"USERNAME": "foo",
|
"USERNAME": "__BITCOIN_RPC_USER__",
|
||||||
"PASSWORD": "bar"
|
"PASSWORD": "__BITCOIN_RPC_PASS__"
|
||||||
},
|
},
|
||||||
"ESPLORA": {
|
"ESPLORA": {
|
||||||
"REST_API_URL": "http://127.0.0.1:4003"
|
"REST_API_URL": "http://127.0.0.1:4003"
|
||||||
@ -23,9 +23,9 @@
|
|||||||
"ENABLED": true,
|
"ENABLED": true,
|
||||||
"HOST": "127.0.0.1",
|
"HOST": "127.0.0.1",
|
||||||
"PORT": 3306,
|
"PORT": 3306,
|
||||||
"USERNAME": "smempool",
|
"USERNAME": "mempool_signet",
|
||||||
"PASSWORD": "smempool",
|
"PASSWORD": "mempool_signet",
|
||||||
"DATABASE": "smempool"
|
"DATABASE": "mempool_signet"
|
||||||
},
|
},
|
||||||
"STATISTICS": {
|
"STATISTICS": {
|
||||||
"ENABLED": true,
|
"ENABLED": true,
|
||||||
|
@ -13,8 +13,8 @@
|
|||||||
},
|
},
|
||||||
"CORE_RPC": {
|
"CORE_RPC": {
|
||||||
"PORT": 18332,
|
"PORT": 18332,
|
||||||
"USERNAME": "foo",
|
"USERNAME": "__BITCOIN_RPC_USER__",
|
||||||
"PASSWORD": "bar"
|
"PASSWORD": "__BITCOIN_RPC_PASS__"
|
||||||
},
|
},
|
||||||
"ESPLORA": {
|
"ESPLORA": {
|
||||||
"REST_API_URL": "http://127.0.0.1:4002"
|
"REST_API_URL": "http://127.0.0.1:4002"
|
||||||
@ -23,9 +23,9 @@
|
|||||||
"ENABLED": true,
|
"ENABLED": true,
|
||||||
"HOST": "127.0.0.1",
|
"HOST": "127.0.0.1",
|
||||||
"PORT": 3306,
|
"PORT": 3306,
|
||||||
"USERNAME": "tmempool",
|
"USERNAME": "mempool_testnet",
|
||||||
"PASSWORD": "tmempool",
|
"PASSWORD": "mempool_testnet",
|
||||||
"DATABASE": "tmempool"
|
"DATABASE": "mempool_testnet"
|
||||||
},
|
},
|
||||||
"STATISTICS": {
|
"STATISTICS": {
|
||||||
"ENABLED": true,
|
"ENABLED": true,
|
||||||
|
14
production/mempool-frontend-config.bisq.json
Normal file
14
production/mempool-frontend-config.bisq.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"BASE_MODULE": "bisq",
|
||||||
|
"OFFICIAL_MEMPOOL_SPACE": true,
|
||||||
|
"TESTNET_ENABLED": true,
|
||||||
|
"LIQUID_ENABLED": true,
|
||||||
|
"LIQUID_TESTNET_ENABLED": true,
|
||||||
|
"BISQ_ENABLED": true,
|
||||||
|
"BISQ_SEPARATE_BACKEND": true,
|
||||||
|
"SIGNET_ENABLED": true,
|
||||||
|
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
|
||||||
|
"LIQUID_WEBSITE_URL": "https://liquid.network",
|
||||||
|
"BISQ_WEBSITE_URL": "https://bisq.markets",
|
||||||
|
"ITEMS_PER_PAGE": 25
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"TESTNET_ENABLED": true,
|
|
||||||
"LIQUID_ENABLED": true,
|
|
||||||
"LIQUID_TESTNET_ENABLED": true,
|
|
||||||
"BISQ_ENABLED": true,
|
|
||||||
"BISQ_SEPARATE_BACKEND": true,
|
|
||||||
"SIGNET_ENABLED": true,
|
|
||||||
"ITEMS_PER_PAGE": 25,
|
|
||||||
"KEEP_BLOCKS_AMOUNT": 8,
|
|
||||||
"NGINX_PROTOCOL": "http",
|
|
||||||
"NGINX_HOSTNAME": "127.0.0.1",
|
|
||||||
"NGINX_PORT": "80"
|
|
||||||
}
|
|
17
production/mempool-frontend-config.liquid.json
Normal file
17
production/mempool-frontend-config.liquid.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"BASE_MODULE": "liquid",
|
||||||
|
"OFFICIAL_MEMPOOL_SPACE": true,
|
||||||
|
"TESTNET_ENABLED": true,
|
||||||
|
"LIQUID_ENABLED": true,
|
||||||
|
"LIQUID_TESTNET_ENABLED": true,
|
||||||
|
"BISQ_ENABLED": true,
|
||||||
|
"BISQ_SEPARATE_BACKEND": true,
|
||||||
|
"SIGNET_ENABLED": true,
|
||||||
|
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
|
||||||
|
"LIQUID_WEBSITE_URL": "https://liquid.network",
|
||||||
|
"BISQ_WEBSITE_URL": "https://bisq.markets",
|
||||||
|
"ITEMS_PER_PAGE": 25,
|
||||||
|
"BLOCK_WEIGHT_UNITS": 300000,
|
||||||
|
"MEMPOOL_BLOCKS_AMOUNT": 2,
|
||||||
|
"KEEP_BLOCKS_AMOUNT": 16
|
||||||
|
}
|
13
production/mempool-frontend-config.mainnet.json
Normal file
13
production/mempool-frontend-config.mainnet.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"OFFICIAL_MEMPOOL_SPACE": true,
|
||||||
|
"TESTNET_ENABLED": true,
|
||||||
|
"LIQUID_ENABLED": true,
|
||||||
|
"LIQUID_TESTNET_ENABLED": true,
|
||||||
|
"BISQ_ENABLED": true,
|
||||||
|
"BISQ_SEPARATE_BACKEND": true,
|
||||||
|
"SIGNET_ENABLED": true,
|
||||||
|
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
|
||||||
|
"LIQUID_WEBSITE_URL": "https://liquid.network",
|
||||||
|
"BISQ_WEBSITE_URL": "https://bisq.markets",
|
||||||
|
"ITEMS_PER_PAGE": 25
|
||||||
|
}
|
@ -1,22 +0,0 @@
|
|||||||
#!/usr/local/bin/zsh
|
|
||||||
export NVM_DIR="$HOME/.nvm"
|
|
||||||
source "$NVM_DIR/nvm.sh"
|
|
||||||
for site in mainnet liquid testnet bisq signet liquidtestnet
|
|
||||||
do
|
|
||||||
#git clone https://github.com/mempool/mempool "${HOME}/${site}"
|
|
||||||
mkdir -p "${HOME}/public_html/${site}/"
|
|
||||||
|
|
||||||
cd "${HOME}/${site}/backend/"
|
|
||||||
cp "../production/mempool-config.${site}.json" "mempool-config.json"
|
|
||||||
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
if [ "${site}" = "mainnet" ]
|
|
||||||
then
|
|
||||||
cd "${HOME}/${site}/frontend/"
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
rsync -av ./dist/mempool/browser/* "${HOME}/public_html/${site}/"
|
|
||||||
fi
|
|
||||||
done
|
|
Loading…
Reference in New Issue
Block a user