Merge pull request #1554 from mempool/nymkappa/bugfix/fast-forward-re-org
Validate block hash chain after indexing and for new blocks
This commit is contained in:
		
						commit
						3189525bb6
					
				| @ -13,6 +13,8 @@ import blocksRepository from '../repositories/BlocksRepository'; | |||||||
| import loadingIndicators from './loading-indicators'; | import loadingIndicators from './loading-indicators'; | ||||||
| import BitcoinApi from './bitcoin/bitcoin-api'; | import BitcoinApi from './bitcoin/bitcoin-api'; | ||||||
| import { prepareBlock } from '../utils/blocks-utils'; | import { prepareBlock } from '../utils/blocks-utils'; | ||||||
|  | import BlocksRepository from '../repositories/BlocksRepository'; | ||||||
|  | import HashratesRepository from '../repositories/HashratesRepository'; | ||||||
| 
 | 
 | ||||||
| class Blocks { | class Blocks { | ||||||
|   private blocks: BlockExtended[] = []; |   private blocks: BlockExtended[] = []; | ||||||
| @ -23,7 +25,7 @@ class Blocks { | |||||||
|   private newBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) => void)[] = []; |   private newBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) => void)[] = []; | ||||||
|   private blockIndexingStarted = false; |   private blockIndexingStarted = false; | ||||||
|   public blockIndexingCompleted = false; |   public blockIndexingCompleted = false; | ||||||
|   public reindexFlag = true; // Always re-index the latest indexed data in case the node went offline with an invalid block tip (reorg)
 |   public reindexFlag = false; | ||||||
| 
 | 
 | ||||||
|   constructor() { } |   constructor() { } | ||||||
| 
 | 
 | ||||||
| @ -272,10 +274,13 @@ class Blocks { | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     this.blockIndexingCompleted = true; |     const chainValid = await BlocksRepository.$validateChain(); | ||||||
|  |     this.reindexFlag = !chainValid; | ||||||
|  |     this.blockIndexingCompleted = chainValid; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   public async $updateBlocks() { |   public async $updateBlocks() { | ||||||
|  |     let fastForwarded = false; | ||||||
|     const blockHeightTip = await bitcoinApi.$getBlockHeightTip(); |     const blockHeightTip = await bitcoinApi.$getBlockHeightTip(); | ||||||
| 
 | 
 | ||||||
|     if (this.blocks.length === 0) { |     if (this.blocks.length === 0) { | ||||||
| @ -287,6 +292,7 @@ class Blocks { | |||||||
|     if (blockHeightTip - this.currentBlockHeight > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 2) { |     if (blockHeightTip - this.currentBlockHeight > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 2) { | ||||||
|       logger.info(`${blockHeightTip - this.currentBlockHeight} blocks since tip. Fast forwarding to the ${config.MEMPOOL.INITIAL_BLOCKS_AMOUNT} recent blocks`); |       logger.info(`${blockHeightTip - this.currentBlockHeight} blocks since tip. Fast forwarding to the ${config.MEMPOOL.INITIAL_BLOCKS_AMOUNT} recent blocks`); | ||||||
|       this.currentBlockHeight = blockHeightTip - config.MEMPOOL.INITIAL_BLOCKS_AMOUNT; |       this.currentBlockHeight = blockHeightTip - config.MEMPOOL.INITIAL_BLOCKS_AMOUNT; | ||||||
|  |       fastForwarded = true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (!this.lastDifficultyAdjustmentTime) { |     if (!this.lastDifficultyAdjustmentTime) { | ||||||
| @ -324,12 +330,18 @@ class Blocks { | |||||||
|       const blockExtended: BlockExtended = await this.$getBlockExtended(block, transactions); |       const blockExtended: BlockExtended = await this.$getBlockExtended(block, transactions); | ||||||
| 
 | 
 | ||||||
|       if (Common.indexingEnabled()) { |       if (Common.indexingEnabled()) { | ||||||
|         await blocksRepository.$saveBlockInDatabase(blockExtended); |         if (!fastForwarded) { | ||||||
| 
 |           const lastBlock = await blocksRepository.$getBlockByHeight(blockExtended.height - 1); | ||||||
|         // If the last 10 blocks chain is not valid, re-index them (reorg)
 |           if (lastBlock !== null && blockExtended.previousblockhash !== lastBlock['hash']) { | ||||||
|         const chainValid = await blocksRepository.$validateRecentBlocks(); |             logger.warn(`Chain divergence detected at block ${lastBlock['height']}, re-indexing most recent data`); | ||||||
|         if (!chainValid) { |             // We assume there won't be a reorg with more than 10 block depth
 | ||||||
|           this.reindexFlag = true; |             await BlocksRepository.$deleteBlocksFrom(lastBlock['height'] - 10); | ||||||
|  |             await HashratesRepository.$deleteLastEntries(); | ||||||
|  |             for (let i = 10; i >= 0; --i) { | ||||||
|  |               await this.$indexBlock(lastBlock['height'] - i); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |           await blocksRepository.$saveBlockInDatabase(blockExtended); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -188,10 +188,6 @@ class Server { | |||||||
| 
 | 
 | ||||||
|     try { |     try { | ||||||
|       await poolsUpdater.updatePoolsJson(); |       await poolsUpdater.updatePoolsJson(); | ||||||
|       if (blocks.reindexFlag) { |  | ||||||
|         await BlocksRepository.$deleteBlocks(10); |  | ||||||
|         await HashratesRepository.$deleteLastEntries(); |  | ||||||
|       } |  | ||||||
|       await blocks.$generateBlockDatabase(); |       await blocks.$generateBlockDatabase(); | ||||||
|       await mining.$generateNetworkHashrateHistory(); |       await mining.$generateNetworkHashrateHistory(); | ||||||
|       await mining.$generatePoolHashrateHistory(); |       await mining.$generatePoolHashrateHistory(); | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ import logger from '../logger'; | |||||||
| import { Common } from '../api/common'; | import { Common } from '../api/common'; | ||||||
| import { prepareBlock } from '../utils/blocks-utils'; | import { prepareBlock } from '../utils/blocks-utils'; | ||||||
| import PoolsRepository from './PoolsRepository'; | import PoolsRepository from './PoolsRepository'; | ||||||
|  | import HashratesRepository from './HashratesRepository'; | ||||||
| 
 | 
 | ||||||
| class BlocksRepository { | class BlocksRepository { | ||||||
|   /** |   /** | ||||||
| @ -370,15 +371,43 @@ class BlocksRepository { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * Delete $count blocks from the database |    * Check if the chain of block hash is valid and delete data from the stale branch if needed | ||||||
|    */ |    */ | ||||||
|   public async $deleteBlocks(count: number) { |   public async $validateChain(): Promise<boolean> { | ||||||
|     logger.info(`Delete ${count} most recent indexed blocks from the database`); |     try { | ||||||
|  |       const start = new Date().getTime(); | ||||||
|  |       const [blocks]: any[] = await DB.query(`SELECT height, hash, previous_block_hash,
 | ||||||
|  |         UNIX_TIMESTAMP(blockTimestamp) as timestamp FROM blocks ORDER BY height`);
 | ||||||
|  | 
 | ||||||
|  |       let currentHeight = 1; | ||||||
|  |       while (currentHeight < blocks.length) { | ||||||
|  |         if (blocks[currentHeight].previous_block_hash !== blocks[currentHeight - 1].hash) { | ||||||
|  |           logger.warn(`Chain divergence detected at block ${blocks[currentHeight - 1].height}, re-indexing newer blocks and hashrates`); | ||||||
|  |           await this.$deleteBlocksFrom(blocks[currentHeight - 1].height); | ||||||
|  |           await HashratesRepository.$deleteHashratesFromTimestamp(blocks[currentHeight - 1].timestamp - 604800); | ||||||
|  |           return false; | ||||||
|  |         } | ||||||
|  |         ++currentHeight; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       logger.info(`${currentHeight} blocks hash validated in ${new Date().getTime() - start} ms`); | ||||||
|  |       return true; | ||||||
|  |     } catch (e) { | ||||||
|  |       logger.err('Cannot validate chain of block hash. Reason: ' + (e instanceof Error ? e.message : e)); | ||||||
|  |       return true; // Don't do anything if there is a db error
 | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Delete blocks from the database from blockHeight | ||||||
|  |    */ | ||||||
|  |   public async $deleteBlocksFrom(blockHeight: number) { | ||||||
|  |     logger.info(`Delete newer blocks from height ${blockHeight} from the database`); | ||||||
| 
 | 
 | ||||||
|     try { |     try { | ||||||
|       await DB.query(`DELETE FROM blocks ORDER BY height DESC LIMIT ${count};`); |       await DB.query(`DELETE FROM blocks where height >= ${blockHeight}`); | ||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       logger.err('Cannot delete recent indexed blocks. Reason: ' + (e instanceof Error ? e.message : e)); |       logger.err('Cannot delete indexed blocks. Reason: ' + (e instanceof Error ? e.message : e)); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -195,6 +195,22 @@ class HashratesRepository { | |||||||
|       logger.err('Cannot delete latest hashrates data points. Reason: ' + (e instanceof Error ? e.message : e)); |       logger.err('Cannot delete latest hashrates data points. Reason: ' + (e instanceof Error ? e.message : e)); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |    | ||||||
|  |   /** | ||||||
|  |    * Delete hashrates from the database from timestamp | ||||||
|  |    */ | ||||||
|  |   public async $deleteHashratesFromTimestamp(timestamp: number) { | ||||||
|  |     logger.info(`Delete newer hashrates from timestamp ${new Date(timestamp * 1000).toUTCString()} from the database`); | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |       await DB.query(`DELETE FROM hashrates WHERE hashrate_timestamp >= FROM_UNIXTIME(?)`, [timestamp]); | ||||||
|  |       // Re-run the hashrate indexing to fill up missing data
 | ||||||
|  |       await this.$setLatestRunTimestamp('last_hashrates_indexing', 0); | ||||||
|  |       await this.$setLatestRunTimestamp('last_weekly_hashrates_indexing', 0); | ||||||
|  |     } catch (e) { | ||||||
|  |       logger.err('Cannot delete latest hashrates data points. Reason: ' + (e instanceof Error ? e.message : e)); | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default new HashratesRepository(); | export default new HashratesRepository(); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user