Merge branch 'master' into nymkappa/feature/hashrate-moving-average
This commit is contained in:
		
						commit
						f9a1f10b99
					
				| @ -13,6 +13,7 @@ | |||||||
|     "INITIAL_BLOCKS_AMOUNT": 8, |     "INITIAL_BLOCKS_AMOUNT": 8, | ||||||
|     "MEMPOOL_BLOCKS_AMOUNT": 8, |     "MEMPOOL_BLOCKS_AMOUNT": 8, | ||||||
|     "INDEXING_BLOCKS_AMOUNT": 11000, |     "INDEXING_BLOCKS_AMOUNT": 11000, | ||||||
|  |     "BLOCKS_SUMMARIES_INDEXING": false, | ||||||
|     "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": [], | ||||||
|  | |||||||
| @ -20,6 +20,7 @@ import indexer from '../indexer'; | |||||||
| import fiatConversion from './fiat-conversion'; | import fiatConversion from './fiat-conversion'; | ||||||
| import RatesRepository from '../repositories/RatesRepository'; | import RatesRepository from '../repositories/RatesRepository'; | ||||||
| import poolsParser from './pools-parser'; | import poolsParser from './pools-parser'; | ||||||
|  | import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository'; | ||||||
| 
 | 
 | ||||||
| class Blocks { | class Blocks { | ||||||
|   private blocks: BlockExtended[] = []; |   private blocks: BlockExtended[] = []; | ||||||
| @ -242,13 +243,68 @@ class Blocks { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /** | ||||||
|  |    * [INDEXING] Index all blocks summaries for the block txs visualization | ||||||
|  |    */ | ||||||
|  |   public async $generateBlocksSummariesDatabase() { | ||||||
|  |     if (Common.blocksSummariesIndexingEnabled() === false) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |       // Get all indexed block hash
 | ||||||
|  |       const indexedBlocks = await blocksRepository.$getIndexedBlocks(); | ||||||
|  |       const indexedBlockSummariesHashesArray = await BlocksSummariesRepository.$getIndexedSummariesId(); | ||||||
|  | 
 | ||||||
|  |       const indexedBlockSummariesHashes = {}; // Use a map for faster seek during the indexing loop
 | ||||||
|  |       for (const hash of indexedBlockSummariesHashesArray) { | ||||||
|  |         indexedBlockSummariesHashes[hash] = true; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       // Logging
 | ||||||
|  |       let newlyIndexed = 0; | ||||||
|  |       let totalIndexed = indexedBlockSummariesHashesArray.length; | ||||||
|  |       let indexedThisRun = 0; | ||||||
|  |       let timer = new Date().getTime() / 1000; | ||||||
|  |       const startedAt = new Date().getTime() / 1000; | ||||||
|  | 
 | ||||||
|  |       for (const block of indexedBlocks) { | ||||||
|  |         if (indexedBlockSummariesHashes[block.hash] === true) { | ||||||
|  |           continue; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Logging
 | ||||||
|  |         const elapsedSeconds = Math.max(1, Math.round((new Date().getTime() / 1000) - timer)); | ||||||
|  |         if (elapsedSeconds > 5) { | ||||||
|  |           const runningFor = Math.max(1, Math.round((new Date().getTime() / 1000) - startedAt)); | ||||||
|  |           const blockPerSeconds = Math.max(1, indexedThisRun / elapsedSeconds); | ||||||
|  |           const progress = Math.round(totalIndexed / indexedBlocks.length * 10000) / 100; | ||||||
|  |           const timeLeft = Math.round((indexedBlocks.length - totalIndexed) / blockPerSeconds); | ||||||
|  |           logger.debug(`Indexing block summary for #${block.height} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${totalIndexed}/${indexedBlocks.length} (${progress}%) | elapsed: ${runningFor} seconds | left: ~${timeLeft} seconds`); | ||||||
|  |           timer = new Date().getTime() / 1000; | ||||||
|  |           indexedThisRun = 0; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         await this.$getStrippedBlockTransactions(block.hash, true, true); // This will index the block summary
 | ||||||
|  | 
 | ||||||
|  |         // Logging
 | ||||||
|  |         indexedThisRun++; | ||||||
|  |         totalIndexed++; | ||||||
|  |         newlyIndexed++; | ||||||
|  |       } | ||||||
|  |       logger.notice(`Blocks summaries indexing completed: indexed ${newlyIndexed} blocks`); | ||||||
|  |     } catch (e) { | ||||||
|  |       logger.err(`Blocks summaries indexing failed. Reason: ${(e instanceof Error ? e.message : e)}`); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   /** |   /** | ||||||
|    * [INDEXING] Index all blocks metadata for the mining dashboard |    * [INDEXING] Index all blocks metadata for the mining dashboard | ||||||
|    */ |    */ | ||||||
|   public async $generateBlockDatabase() { |   public async $generateBlockDatabase(): Promise<boolean> { | ||||||
|     const blockchainInfo = await bitcoinClient.getBlockchainInfo(); |     const blockchainInfo = await bitcoinClient.getBlockchainInfo(); | ||||||
|     if (blockchainInfo.blocks !== blockchainInfo.headers) { // Wait for node to sync
 |     if (blockchainInfo.blocks !== blockchainInfo.headers) { // Wait for node to sync
 | ||||||
|       return; |       return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     try { |     try { | ||||||
| @ -292,7 +348,7 @@ class Blocks { | |||||||
|           const elapsedSeconds = Math.max(1, Math.round((new Date().getTime() / 1000) - timer)); |           const elapsedSeconds = Math.max(1, Math.round((new Date().getTime() / 1000) - timer)); | ||||||
|           if (elapsedSeconds > 5 || blockHeight === lastBlockToIndex) { |           if (elapsedSeconds > 5 || blockHeight === lastBlockToIndex) { | ||||||
|             const runningFor = Math.max(1, Math.round((new Date().getTime() / 1000) - startedAt)); |             const runningFor = Math.max(1, Math.round((new Date().getTime() / 1000) - startedAt)); | ||||||
|             const blockPerSeconds = Math.max(1, Math.round(indexedThisRun / elapsedSeconds)); |             const blockPerSeconds = Math.max(1, indexedThisRun / elapsedSeconds); | ||||||
|             const progress = Math.round(totalIndexed / indexingBlockAmount * 10000) / 100; |             const progress = Math.round(totalIndexed / indexingBlockAmount * 10000) / 100; | ||||||
|             const timeLeft = Math.round((indexingBlockAmount - totalIndexed) / blockPerSeconds); |             const timeLeft = Math.round((indexingBlockAmount - totalIndexed) / blockPerSeconds); | ||||||
|             logger.debug(`Indexing block #${blockHeight} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${totalIndexed}/${indexingBlockAmount} (${progress}%) | elapsed: ${runningFor} seconds | left: ~${timeLeft} seconds`); |             logger.debug(`Indexing block #${blockHeight} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${totalIndexed}/${indexingBlockAmount} (${progress}%) | elapsed: ${runningFor} seconds | left: ~${timeLeft} seconds`); | ||||||
| @ -316,13 +372,16 @@ class Blocks { | |||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       logger.err('Block indexing failed. Trying again later. Reason: ' + (e instanceof Error ? e.message : e)); |       logger.err('Block indexing failed. Trying again later. Reason: ' + (e instanceof Error ? e.message : e)); | ||||||
|       loadingIndicators.setProgress('block-indexing', 100); |       loadingIndicators.setProgress('block-indexing', 100); | ||||||
|       return; |       return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const chainValid = await BlocksRepository.$validateChain(); |     const chainValid = await BlocksRepository.$validateChain(); | ||||||
|     if (!chainValid) { |     if (!chainValid) { | ||||||
|       indexer.reindex(); |       indexer.reindex(); | ||||||
|  |       return false; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public async $updateBlocks() { |   public async $updateBlocks() { | ||||||
| @ -387,11 +446,19 @@ class Blocks { | |||||||
|             // We assume there won't be a reorg with more than 10 block depth
 |             // We assume there won't be a reorg with more than 10 block depth
 | ||||||
|             await BlocksRepository.$deleteBlocksFrom(lastBlock['height'] - 10); |             await BlocksRepository.$deleteBlocksFrom(lastBlock['height'] - 10); | ||||||
|             await HashratesRepository.$deleteLastEntries(); |             await HashratesRepository.$deleteLastEntries(); | ||||||
|  |             await BlocksSummariesRepository.$deleteBlocksFrom(lastBlock['height'] - 10); | ||||||
|             for (let i = 10; i >= 0; --i) { |             for (let i = 10; i >= 0; --i) { | ||||||
|               await this.$indexBlock(lastBlock['height'] - i); |               const newBlock = await this.$indexBlock(lastBlock['height'] - i); | ||||||
|  |               await this.$getStrippedBlockTransactions(newBlock.id, true, true); | ||||||
|             } |             } | ||||||
|  |             logger.info(`Re-indexed 10 blocks and summaries`); | ||||||
|           } |           } | ||||||
|           await blocksRepository.$saveBlockInDatabase(blockExtended); |           await blocksRepository.$saveBlockInDatabase(blockExtended); | ||||||
|  | 
 | ||||||
|  |           // Save blocks summary for visualization if it's enabled
 | ||||||
|  |           if (Common.blocksSummariesIndexingEnabled() === true) { | ||||||
|  |             await this.$getStrippedBlockTransactions(blockExtended.id, true); | ||||||
|  |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       if (fiatConversion.ratesInitialized === true && config.DATABASE.ENABLED === true) { |       if (fiatConversion.ratesInitialized === true && config.DATABASE.ENABLED === true) { | ||||||
| @ -477,14 +544,34 @@ class Blocks { | |||||||
|     return blockExtended; |     return blockExtended; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public async $getStrippedBlockTransactions(hash: string): Promise<TransactionStripped[]> { |   public async $getStrippedBlockTransactions(hash: string, skipMemoryCache: boolean = false, | ||||||
|  |     skipDBLookup: boolean = false): Promise<TransactionStripped[]> | ||||||
|  |   { | ||||||
|  |     if (skipMemoryCache === false) { | ||||||
|       // Check the memory cache
 |       // Check the memory cache
 | ||||||
|       const cachedSummary = this.getBlockSummaries().find((b) => b.id === hash); |       const cachedSummary = this.getBlockSummaries().find((b) => b.id === hash); | ||||||
|       if (cachedSummary) { |       if (cachedSummary) { | ||||||
|         return cachedSummary.transactions; |         return cachedSummary.transactions; | ||||||
|       } |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Check if it's indexed in db
 | ||||||
|  |     if (skipDBLookup === false && Common.blocksSummariesIndexingEnabled() === true) { | ||||||
|  |       const indexedSummary = await BlocksSummariesRepository.$getByBlockId(hash); | ||||||
|  |       if (indexedSummary !== undefined) { | ||||||
|  |         return indexedSummary.transactions; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Call Core RPC
 | ||||||
|     const block = await bitcoinClient.getBlock(hash, 2); |     const block = await bitcoinClient.getBlock(hash, 2); | ||||||
|     const summary = this.summarizeBlock(block); |     const summary = this.summarizeBlock(block); | ||||||
|  | 
 | ||||||
|  |     // Index the response if needed
 | ||||||
|  |     if (Common.blocksSummariesIndexingEnabled() === true) { | ||||||
|  |       await BlocksSummariesRepository.$saveSummary(block.height, summary); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     return summary.transactions; |     return summary.transactions; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -177,4 +177,11 @@ export class Common { | |||||||
|       config.MEMPOOL.INDEXING_BLOCKS_AMOUNT !== 0 |       config.MEMPOOL.INDEXING_BLOCKS_AMOUNT !== 0 | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   static blocksSummariesIndexingEnabled(): boolean { | ||||||
|  |     return ( | ||||||
|  |       Common.indexingEnabled() && | ||||||
|  |       config.MEMPOOL.BLOCKS_SUMMARIES_INDEXING === true | ||||||
|  |     ); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ import logger from '../logger'; | |||||||
| import { Common } from './common'; | import { Common } from './common'; | ||||||
| 
 | 
 | ||||||
| class DatabaseMigration { | class DatabaseMigration { | ||||||
|   private static currentVersion = 19; |   private static currentVersion = 20; | ||||||
|   private queryTimeout = 120000; |   private queryTimeout = 120000; | ||||||
|   private statisticsAddedIndexed = false; |   private statisticsAddedIndexed = false; | ||||||
|   private uniqueLogs: string[] = []; |   private uniqueLogs: string[] = []; | ||||||
| @ -217,6 +217,10 @@ class DatabaseMigration { | |||||||
|       if (databaseSchemaVersion < 19) { |       if (databaseSchemaVersion < 19) { | ||||||
|         await this.$executeQuery(this.getCreateRatesTableQuery(), await this.$checkIfTableExists('rates')); |         await this.$executeQuery(this.getCreateRatesTableQuery(), await this.$checkIfTableExists('rates')); | ||||||
|       } |       } | ||||||
|  | 
 | ||||||
|  |       if (databaseSchemaVersion < 20 && isBitcoin === true) { | ||||||
|  |         await this.$executeQuery(this.getCreateBlocksSummariesTableQuery(), await this.$checkIfTableExists('blocks_summaries')); | ||||||
|  |       } | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       throw e; |       throw e; | ||||||
|     } |     } | ||||||
| @ -512,6 +516,16 @@ class DatabaseMigration { | |||||||
|     ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
 |     ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   private getCreateBlocksSummariesTableQuery(): string { | ||||||
|  |     return `CREATE TABLE IF NOT EXISTS blocks_summaries (
 | ||||||
|  |       height int(10) unsigned NOT NULL, | ||||||
|  |       id varchar(65) NOT NULL, | ||||||
|  |       transactions JSON NOT NULL, | ||||||
|  |       PRIMARY KEY (id), | ||||||
|  |       INDEX (height) | ||||||
|  |     ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   public async $truncateIndexedData(tables: string[]) { |   public async $truncateIndexedData(tables: string[]) { | ||||||
|     const allowedTables = ['blocks', 'hashrates']; |     const allowedTables = ['blocks', 'hashrates']; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -15,6 +15,7 @@ interface IConfig { | |||||||
|     INITIAL_BLOCKS_AMOUNT: number; |     INITIAL_BLOCKS_AMOUNT: number; | ||||||
|     MEMPOOL_BLOCKS_AMOUNT: number; |     MEMPOOL_BLOCKS_AMOUNT: number; | ||||||
|     INDEXING_BLOCKS_AMOUNT: number; |     INDEXING_BLOCKS_AMOUNT: number; | ||||||
|  |     BLOCKS_SUMMARIES_INDEXING: boolean; | ||||||
|     PRICE_FEED_UPDATE_INTERVAL: number; |     PRICE_FEED_UPDATE_INTERVAL: number; | ||||||
|     USE_SECOND_NODE_FOR_MINFEE: boolean; |     USE_SECOND_NODE_FOR_MINFEE: boolean; | ||||||
|     EXTERNAL_ASSETS: string[]; |     EXTERNAL_ASSETS: string[]; | ||||||
| @ -104,6 +105,7 @@ const defaults: IConfig = { | |||||||
|     'INITIAL_BLOCKS_AMOUNT': 8, |     'INITIAL_BLOCKS_AMOUNT': 8, | ||||||
|     'MEMPOOL_BLOCKS_AMOUNT': 8, |     'MEMPOOL_BLOCKS_AMOUNT': 8, | ||||||
|     'INDEXING_BLOCKS_AMOUNT': 11000, // 0 = disable indexing, -1 = index all blocks
 |     'INDEXING_BLOCKS_AMOUNT': 11000, // 0 = disable indexing, -1 = index all blocks
 | ||||||
|  |     'BLOCKS_SUMMARIES_INDEXING': false, | ||||||
|     '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': [], | ||||||
|  | |||||||
| @ -29,10 +29,17 @@ class Indexer { | |||||||
|     this.indexerRunning = true; |     this.indexerRunning = true; | ||||||
| 
 | 
 | ||||||
|     try { |     try { | ||||||
|       await blocks.$generateBlockDatabase(); |       const chainValid = await blocks.$generateBlockDatabase(); | ||||||
|  |       if (chainValid === false) { | ||||||
|  |         // Chain of block hash was invalid, so we need to reindex. Stop here and continue at the next iteration
 | ||||||
|  |         this.indexerRunning = false; | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       await this.$resetHashratesIndexingState(); |       await this.$resetHashratesIndexingState(); | ||||||
|       await mining.$generateNetworkHashrateHistory(); |       await mining.$generateNetworkHashrateHistory(); | ||||||
|       await mining.$generatePoolHashrateHistory(); |       await mining.$generatePoolHashrateHistory(); | ||||||
|  |       await blocks.$generateBlocksSummariesDatabase(); | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       this.reindex(); |       this.reindex(); | ||||||
|       logger.err(`Indexer failed, trying again later. Reason: ` + (e instanceof Error ? e.message : e)); |       logger.err(`Indexer failed, trying again later. Reason: ` + (e instanceof Error ? e.message : e)); | ||||||
|  | |||||||
| @ -6,6 +6,7 @@ import { prepareBlock } from '../utils/blocks-utils'; | |||||||
| import PoolsRepository from './PoolsRepository'; | import PoolsRepository from './PoolsRepository'; | ||||||
| import HashratesRepository from './HashratesRepository'; | import HashratesRepository from './HashratesRepository'; | ||||||
| import { escape } from 'mysql2'; | import { escape } from 'mysql2'; | ||||||
|  | import BlocksSummariesRepository from './BlocksSummariesRepository'; | ||||||
| 
 | 
 | ||||||
| class BlocksRepository { | class BlocksRepository { | ||||||
|   /** |   /** | ||||||
| @ -495,6 +496,7 @@ class BlocksRepository { | |||||||
|         if (blocks[idx].previous_block_hash !== blocks[idx - 1].hash) { |         if (blocks[idx].previous_block_hash !== blocks[idx - 1].hash) { | ||||||
|           logger.warn(`Chain divergence detected at block ${blocks[idx - 1].height}, re-indexing newer blocks and hashrates`); |           logger.warn(`Chain divergence detected at block ${blocks[idx - 1].height}, re-indexing newer blocks and hashrates`); | ||||||
|           await this.$deleteBlocksFrom(blocks[idx - 1].height); |           await this.$deleteBlocksFrom(blocks[idx - 1].height); | ||||||
|  |           await BlocksSummariesRepository.$deleteBlocksFrom(blocks[idx - 1].height); | ||||||
|           await HashratesRepository.$deleteHashratesFromTimestamp(blocks[idx - 1].timestamp - 604800); |           await HashratesRepository.$deleteHashratesFromTimestamp(blocks[idx - 1].timestamp - 604800); | ||||||
|           return false; |           return false; | ||||||
|         } |         } | ||||||
| @ -652,6 +654,19 @@ class BlocksRepository { | |||||||
|       throw e; |       throw e; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Get a list of blocks that have been indexed | ||||||
|  |    */ | ||||||
|  |   public async $getIndexedBlocks(): Promise<any[]> { | ||||||
|  |     try { | ||||||
|  |       const [rows]: any = await DB.query(`SELECT height, hash FROM blocks ORDER BY height DESC`); | ||||||
|  |       return rows; | ||||||
|  |     } catch (e) { | ||||||
|  |       logger.err('Cannot generate block size and weight history. Reason: ' + (e instanceof Error ? e.message : e)); | ||||||
|  |       throw e; | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default new BlocksRepository(); | export default new BlocksRepository(); | ||||||
|  | |||||||
							
								
								
									
										59
									
								
								backend/src/repositories/BlocksSummariesRepository.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								backend/src/repositories/BlocksSummariesRepository.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | |||||||
|  | import DB from '../database'; | ||||||
|  | import logger from '../logger'; | ||||||
|  | import { BlockSummary } from '../mempool.interfaces'; | ||||||
|  | 
 | ||||||
|  | class BlocksSummariesRepository { | ||||||
|  |   public async $getByBlockId(id: string): Promise<BlockSummary | undefined> { | ||||||
|  |     try { | ||||||
|  |       const [summary]: any[] = await DB.query(`SELECT * from blocks_summaries WHERE id = ?`, [id]); | ||||||
|  |       if (summary.length > 0) { | ||||||
|  |         summary[0].transactions = JSON.parse(summary[0].transactions); | ||||||
|  |         return summary[0]; | ||||||
|  |       } | ||||||
|  |     } catch (e) { | ||||||
|  |       logger.err(`Cannot get block summary for block id ${id}. Reason: ` + (e instanceof Error ? e.message : e)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return undefined; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public async $saveSummary(height: number, summary: BlockSummary) { | ||||||
|  |     try { | ||||||
|  |       await DB.query(`INSERT INTO blocks_summaries VALUE (?, ?, ?)`, [height, summary.id, JSON.stringify(summary.transactions)]); | ||||||
|  |     } catch (e: any) { | ||||||
|  |       if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
 | ||||||
|  |         logger.debug(`Cannot save block summary for ${summary.id} because it has already been indexed, ignoring`); | ||||||
|  |       } else { | ||||||
|  |         logger.debug(`Cannot save block summary for ${summary.id}. Reason: ${e instanceof Error ? e.message : e}`); | ||||||
|  |         throw e; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public async $getIndexedSummariesId(): Promise<string[]> { | ||||||
|  |     try { | ||||||
|  |       const [rows]: any[] = await DB.query(`SELECT id from blocks_summaries`); | ||||||
|  |       return rows.map(row => row.id); | ||||||
|  |     } catch (e) { | ||||||
|  |       logger.err(`Cannot get block summaries id list. Reason: ` + (e instanceof Error ? e.message : e)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return []; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Delete blocks from the database from blockHeight | ||||||
|  |    */ | ||||||
|  |    public async $deleteBlocksFrom(blockHeight: number) { | ||||||
|  |     logger.info(`Delete newer blocks summary from height ${blockHeight} from the database`); | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |       await DB.query(`DELETE FROM blocks_summaries where height >= ${blockHeight}`); | ||||||
|  |     } catch (e) { | ||||||
|  |       logger.err('Cannot delete indexed blocks summaries. Reason: ' + (e instanceof Error ? e.message : e)); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default new BlocksSummariesRepository(); | ||||||
|  | 
 | ||||||
| @ -729,7 +729,7 @@ class Routes { | |||||||
|   public async getStrippedBlockTransactions(req: Request, res: Response) { |   public async getStrippedBlockTransactions(req: Request, res: Response) { | ||||||
|     try { |     try { | ||||||
|       const transactions = await blocks.$getStrippedBlockTransactions(req.params.hash); |       const transactions = await blocks.$getStrippedBlockTransactions(req.params.hash); | ||||||
|       res.setHeader('Expires', new Date(Date.now() + 1000 * 600).toUTCString()); |       res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24 * 30).toUTCString()); | ||||||
|       res.json(transactions); |       res.json(transactions); | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       res.status(500).send(e instanceof Error ? e.message : e); |       res.status(500).send(e instanceof Error ? e.message : e); | ||||||
|  | |||||||
| @ -19,7 +19,8 @@ | |||||||
|     "EXTERNAL_RETRY_INTERVAL": __MEMPOOL_EXTERNAL_RETRY_INTERVAL__, |     "EXTERNAL_RETRY_INTERVAL": __MEMPOOL_EXTERNAL_RETRY_INTERVAL__, | ||||||
|     "USER_AGENT": "__MEMPOOL_USER_AGENT__", |     "USER_AGENT": "__MEMPOOL_USER_AGENT__", | ||||||
|     "STDOUT_LOG_MIN_PRIORITY": "__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__", |     "STDOUT_LOG_MIN_PRIORITY": "__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__", | ||||||
|     "INDEXING_BLOCKS_AMOUNT": __MEMPOOL_INDEXING_BLOCKS_AMOUNT__ |     "INDEXING_BLOCKS_AMOUNT": __MEMPOOL_INDEXING_BLOCKS_AMOUNT__, | ||||||
|  |     "BLOCKS_SUMMARIES_INDEXING": __MEMPOOL_BLOCKS_SUMMARIES_INDEXING__ | ||||||
|   }, |   }, | ||||||
|   "CORE_RPC": { |   "CORE_RPC": { | ||||||
|     "HOST": "__CORE_RPC_HOST__", |     "HOST": "__CORE_RPC_HOST__", | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ __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:=11000} | __MEMPOOL_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=11000} | ||||||
|  | __MEMPOOL_BLOCKS_SUMMARIES_INDEXING__=${MEMPOOL_BLOCKS_SUMMARIES_INDEXING:=false} | ||||||
| __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:=[]} | __MEMPOOL_EXTERNAL_ASSETS__=${MEMPOOL_EXTERNAL_ASSETS:=[]} | ||||||
| @ -101,6 +102,7 @@ sed -i "s/__MEMPOOL_BLOCK_WEIGHT_UNITS__/${__MEMPOOL_BLOCK_WEIGHT_UNITS__}/g" me | |||||||
| sed -i "s/__MEMPOOL_INITIAL_BLOCKS_AMOUNT__/${__MEMPOOL_INITIAL_BLOCKS_AMOUNT__}/g" mempool-config.json | sed -i "s/__MEMPOOL_INITIAL_BLOCKS_AMOUNT__/${__MEMPOOL_INITIAL_BLOCKS_AMOUNT__}/g" mempool-config.json | ||||||
| sed -i "s/__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__/${__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__}/g" mempool-config.json | sed -i "s/__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__/${__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__}/g" mempool-config.json | ||||||
| sed -i "s/__MEMPOOL_INDEXING_BLOCKS_AMOUNT__/${__MEMPOOL_INDEXING_BLOCKS_AMOUNT__}/g" mempool-config.json | sed -i "s/__MEMPOOL_INDEXING_BLOCKS_AMOUNT__/${__MEMPOOL_INDEXING_BLOCKS_AMOUNT__}/g" mempool-config.json | ||||||
|  | sed -i "s/__MEMPOOL_BLOCKS_SUMMARIES_INDEXING__/${__MEMPOOL_BLOCKS_SUMMARIES_INDEXING__}/g" mempool-config.json | ||||||
| sed -i "s/__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__/${__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__}/g" mempool-config.json | sed -i "s/__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__/${__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__}/g" mempool-config.json | ||||||
| sed -i "s/__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__/${__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__}/g" mempool-config.json | sed -i "s/__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__/${__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__}/g" mempool-config.json | ||||||
| sed -i "s!__MEMPOOL_EXTERNAL_ASSETS__!${__MEMPOOL_EXTERNAL_ASSETS__}!g" mempool-config.json | sed -i "s!__MEMPOOL_EXTERNAL_ASSETS__!${__MEMPOOL_EXTERNAL_ASSETS__}!g" mempool-config.json | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| <div [formGroup]="languageForm" class="text-small text-center mt-4"> | <div [formGroup]="languageForm" class="text-small text-center"> | ||||||
|     <select formControlName="language" class="custom-select custom-select-sm form-control-secondary form-control mx-auto" style="width: 130px;" (change)="changeLanguage()"> |     <select formControlName="language" class="custom-select custom-select-sm form-control-secondary form-control mx-auto" style="width: 130px;" (change)="changeLanguage()"> | ||||||
|         <option *ngFor="let lang of languages" [value]="lang.code">{{ lang.name }}</option> |         <option *ngFor="let lang of languages" [value]="lang.code">{{ lang.name }}</option> | ||||||
|     </select> |     </select> | ||||||
|  | |||||||
| @ -1,34 +1,6 @@ | |||||||
| 
 | 
 | ||||||
| <div class="container-xl dashboard-container"> | <div class="container-xl dashboard-container"> | ||||||
|   <div class="row row-cols-1 row-cols-md-2" *ngIf="{ value: (mempoolInfoData$ | async) } as mempoolInfoData"> |   <div class="row row-cols-1 row-cols-md-2" *ngIf="{ value: (mempoolInfoData$ | async) } as mempoolInfoData"> | ||||||
|     <ng-template [ngIf]="collapseLevel === 'three'" [ngIfElse]="expanded"> |  | ||||||
|       <div class="col card-wrapper" *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'"> |  | ||||||
|         <div class="main-title" i18n="fees-box.transaction-fees">Transaction Fees</div> |  | ||||||
|         <div class="card"> |  | ||||||
|           <div class="card-body less-padding"> |  | ||||||
|             <app-fees-box class="d-block"></app-fees-box> |  | ||||||
|           </div> |  | ||||||
|         </div> |  | ||||||
|       </div> |  | ||||||
|       <div class="col" *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'"> |  | ||||||
|         <app-difficulty></app-difficulty> |  | ||||||
|       </div> |  | ||||||
|       <div class="col"> |  | ||||||
|         <div class="card"> |  | ||||||
|           <div class="card-body"> |  | ||||||
|             <ng-container *ngTemplateOutlet="stateService.network === 'liquid' ? lbtcPegs : mempoolTable; context: { $implicit: mempoolInfoData }"></ng-container> |  | ||||||
|           </div> |  | ||||||
|         </div> |  | ||||||
|       </div> |  | ||||||
|       <div class="col"> |  | ||||||
|         <div class="card"> |  | ||||||
|           <div class="card-body"> |  | ||||||
|             <ng-container *ngTemplateOutlet="stateService.network === 'liquid' ? mempoolTable : txPerSecond; context: { $implicit: mempoolInfoData }"></ng-container> |  | ||||||
|           </div> |  | ||||||
|         </div> |  | ||||||
|       </div> |  | ||||||
|     </ng-template> |  | ||||||
|     <ng-template #expanded> |  | ||||||
|     <ng-container *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'"> |     <ng-container *ngIf="(network$ | async) !== 'liquid' && (network$ | async) !== 'liquidtestnet'"> | ||||||
|       <div class="col card-wrapper"> |       <div class="col card-wrapper"> | ||||||
|         <div class="main-title" i18n="fees-box.transaction-fees">Transaction Fees</div> |         <div class="main-title" i18n="fees-box.transaction-fees">Transaction Fees</div> | ||||||
| @ -102,7 +74,6 @@ | |||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|       <ng-template [ngIf]="collapseLevel === 'one'"> |  | ||||||
|     <div class="col" style="max-height: 410px"> |     <div class="col" style="max-height: 410px"> | ||||||
|       <div class="card"> |       <div class="card"> | ||||||
|         <div class="card-body"> |         <div class="card-body"> | ||||||
| @ -163,20 +134,12 @@ | |||||||
|               </tr> |               </tr> | ||||||
|             </tbody> |             </tbody> | ||||||
|           </table> |           </table> | ||||||
|  |           <div class=""> </div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|     </ng-template> |  | ||||||
|     </ng-template> |  | ||||||
|   </div> |   </div> | ||||||
| 
 | 
 | ||||||
|   <button type="button" class="btn btn-secondary btn-sm d-block mx-auto" (click)="toggleCollapsed()"> |  | ||||||
|     <div [ngSwitch]="collapseLevel"> |  | ||||||
|       <fa-icon *ngSwitchCase="'three'" [icon]="['fas', 'angle-down']" [fixedWidth]="true" i18n-title="dashboard.expand" title="Expand"></fa-icon> |  | ||||||
|       <fa-icon *ngSwitchDefault [icon]="['fas', 'angle-up']" [fixedWidth]="true" i18n-title="dashboard.collapse" title="Collapse"></fa-icon> |  | ||||||
|     </div> |  | ||||||
|   </button> |  | ||||||
| 
 |  | ||||||
|   <app-language-selector></app-language-selector> |   <app-language-selector></app-language-selector> | ||||||
| 
 | 
 | ||||||
|   <div class="terms-of-service"> |   <div class="terms-of-service"> | ||||||
|  | |||||||
| @ -33,7 +33,6 @@ interface MempoolStatsData { | |||||||
|   changeDetection: ChangeDetectionStrategy.OnPush |   changeDetection: ChangeDetectionStrategy.OnPush | ||||||
| }) | }) | ||||||
| export class DashboardComponent implements OnInit { | export class DashboardComponent implements OnInit { | ||||||
|   collapseLevel: string; |  | ||||||
|   featuredAssets$: Observable<any>; |   featuredAssets$: Observable<any>; | ||||||
|   network$: Observable<string>; |   network$: Observable<string>; | ||||||
|   mempoolBlocksData$: Observable<MempoolBlocksData>; |   mempoolBlocksData$: Observable<MempoolBlocksData>; | ||||||
| @ -63,7 +62,6 @@ export class DashboardComponent implements OnInit { | |||||||
|     this.seoService.resetTitle(); |     this.seoService.resetTitle(); | ||||||
|     this.websocketService.want(['blocks', 'stats', 'mempool-blocks', 'live-2h-chart']); |     this.websocketService.want(['blocks', 'stats', 'mempool-blocks', 'live-2h-chart']); | ||||||
|     this.network$ = merge(of(''), this.stateService.networkChanged$); |     this.network$ = merge(of(''), this.stateService.networkChanged$); | ||||||
|     this.collapseLevel = this.storageService.getValue('dashboard-collapsed') || 'one'; |  | ||||||
|     this.mempoolLoadingStatus$ = this.stateService.loadingIndicators$ |     this.mempoolLoadingStatus$ = this.stateService.loadingIndicators$ | ||||||
|       .pipe( |       .pipe( | ||||||
|         map((indicators) => indicators.mempool !== undefined ? indicators.mempool : 100) |         map((indicators) => indicators.mempool !== undefined ? indicators.mempool : 100) | ||||||
| @ -230,15 +228,4 @@ export class DashboardComponent implements OnInit { | |||||||
|   trackByBlock(index: number, block: BlockExtended) { |   trackByBlock(index: number, block: BlockExtended) { | ||||||
|     return block.height; |     return block.height; | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
|   toggleCollapsed() { |  | ||||||
|     if (this.collapseLevel === 'one') { |  | ||||||
|       this.collapseLevel = 'two'; |  | ||||||
|     } else if (this.collapseLevel === 'two') { |  | ||||||
|       this.collapseLevel = 'three'; |  | ||||||
|     } else { |  | ||||||
|       this.collapseLevel = 'one'; |  | ||||||
|     } |  | ||||||
|     this.storageService.setValue('dashboard-collapsed', this.collapseLevel); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import { HttpClient, HttpParams } from '@angular/common/http'; | import { HttpClient, HttpParams } from '@angular/common/http'; | ||||||
| import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators, | import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators, | ||||||
|          PoolsStats, PoolStat, BlockExtended, TransactionStripped, RewardStats } from '../interfaces/node-api.interface'; |   PoolStat, BlockExtended, TransactionStripped, RewardStats } from '../interfaces/node-api.interface'; | ||||||
| import { Observable } from 'rxjs'; | import { Observable } from 'rxjs'; | ||||||
| import { StateService } from './state.service'; | import { StateService } from './state.service'; | ||||||
| import { WebsocketResponse } from '../interfaces/websocket.interface'; | import { WebsocketResponse } from '../interfaces/websocket.interface'; | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ | |||||||
|     "CLEAR_PROTECTION_MINUTES": 5, |     "CLEAR_PROTECTION_MINUTES": 5, | ||||||
|     "POLL_RATE_MS": 1000, |     "POLL_RATE_MS": 1000, | ||||||
|     "INDEXING_BLOCKS_AMOUNT": -1, |     "INDEXING_BLOCKS_AMOUNT": -1, | ||||||
|  |     "BLOCKS_SUMMARIES_INDEXING": true, | ||||||
|     "USE_SECOND_NODE_FOR_MINFEE": true |     "USE_SECOND_NODE_FOR_MINFEE": true | ||||||
|   }, |   }, | ||||||
|   "SYSLOG" : { |   "SYSLOG" : { | ||||||
|  | |||||||
| @ -4,14 +4,42 @@ location /api/v1/statistics { | |||||||
| location /api/v1/mining { | location /api/v1/mining { | ||||||
| 	try_files /dev/null @mempool-api-v1-warmcache; | 	try_files /dev/null @mempool-api-v1-warmcache; | ||||||
| } | } | ||||||
|  | location /api/v1/block { | ||||||
|  | 	try_files /dev/null @mempool-api-v1-forevercache; | ||||||
|  | } | ||||||
| location /api/v1 { | location /api/v1 { | ||||||
| 	try_files /dev/null @mempool-api-v1-coldcache; | 	try_files /dev/null @mempool-api-v1-coldcache; | ||||||
| } | } | ||||||
|  | location /api/block { | ||||||
|  | 	rewrite ^/api/(.*) /$1 break; | ||||||
|  | 	try_files /dev/null @electrs-api-forevercache; | ||||||
|  | } | ||||||
| location /api/ { | location /api/ { | ||||||
| 	rewrite ^/api/(.*) /$1 break; | 	rewrite ^/api/(.*) /$1 break; | ||||||
| 	try_files /dev/null @electrs-api-nocache; | 	try_files /dev/null @electrs-api-nocache; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | location @mempool-api-v1-forevercache { | ||||||
|  | 	proxy_pass $mempoolBackend; | ||||||
|  | 	proxy_http_version 1.1; | ||||||
|  | 
 | ||||||
|  | 	proxy_set_header Host $http_host; | ||||||
|  | 	proxy_set_header X-Real-IP $remote_addr; | ||||||
|  | 	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||||||
|  | 	proxy_set_header Upgrade $http_upgrade; | ||||||
|  | 	proxy_set_header Connection "upgrade"; | ||||||
|  | 	proxy_set_header X-Forwarded-Proto $scheme; | ||||||
|  | 
 | ||||||
|  | 	proxy_cache_bypass $http_upgrade; | ||||||
|  | 	proxy_cache_background_update on; | ||||||
|  | 	proxy_cache_use_stale updating; | ||||||
|  | 	proxy_cache api; | ||||||
|  | 	proxy_cache_valid 200 30d; | ||||||
|  | 	proxy_redirect off; | ||||||
|  | 
 | ||||||
|  | 	expires 30d; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| location @mempool-api-v1-warmcache { | location @mempool-api-v1-warmcache { | ||||||
| 	proxy_pass $mempoolBackend; | 	proxy_pass $mempoolBackend; | ||||||
| 	proxy_http_version 1.1; | 	proxy_http_version 1.1; | ||||||
| @ -46,6 +74,7 @@ location @mempool-api-v1-coldcache { | |||||||
| 	proxy_cache api; | 	proxy_cache api; | ||||||
| 	proxy_cache_valid 200 10s; | 	proxy_cache_valid 200 10s; | ||||||
| 	proxy_redirect off; | 	proxy_redirect off; | ||||||
|  | 
 | ||||||
| 	expires 10s; | 	expires 10s; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -81,4 +110,27 @@ location @electrs-api-nocache { | |||||||
| 	proxy_cache_bypass $http_upgrade; | 	proxy_cache_bypass $http_upgrade; | ||||||
| 	proxy_redirect off; | 	proxy_redirect off; | ||||||
| 	proxy_buffering off; | 	proxy_buffering off; | ||||||
|  | 
 | ||||||
|  | 	expires -1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | location @electrs-api-forevercache { | ||||||
|  | 	proxy_pass $electrsBackend; | ||||||
|  | 	proxy_http_version 1.1; | ||||||
|  | 
 | ||||||
|  | 	proxy_set_header Host $http_host; | ||||||
|  | 	proxy_set_header X-Real-IP $remote_addr; | ||||||
|  | 	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||||||
|  | 	proxy_set_header Upgrade $http_upgrade; | ||||||
|  | 	proxy_set_header Connection "upgrade"; | ||||||
|  | 	proxy_set_header X-Forwarded-Proto $scheme; | ||||||
|  | 
 | ||||||
|  | 	proxy_cache_bypass $http_upgrade; | ||||||
|  | 	proxy_cache_background_update on; | ||||||
|  | 	proxy_cache_use_stale updating; | ||||||
|  | 	proxy_cache api; | ||||||
|  | 	proxy_cache_valid 200 30d; | ||||||
|  | 	proxy_redirect off; | ||||||
|  | 
 | ||||||
|  | 	expires 30d; | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user