From 281899f5514417224be4275873b4804a3323dd7e Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Sat, 18 Feb 2023 14:10:07 +0900 Subject: [PATCH] List orphaned blocks in the new blocks-bulk API --- backend/src/api/blocks.ts | 8 ++++- backend/src/api/chain-tips.ts | 53 +++++++++++++++++++++++++++++++ backend/src/index.ts | 2 ++ backend/src/mempool.interfaces.ts | 3 +- 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 backend/src/api/chain-tips.ts diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index ccf7bd2f4..25c199de9 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -25,6 +25,7 @@ import mining from './mining/mining'; import DifficultyAdjustmentsRepository from '../repositories/DifficultyAdjustmentsRepository'; import PricesRepository from '../repositories/PricesRepository'; import priceUpdater from '../tasks/price-updater'; +import chainTips from './chain-tips'; class Blocks { private blocks: BlockExtended[] = []; @@ -171,6 +172,7 @@ class Blocks { blk.extras.coinbaseRaw = blk.extras.coinbaseTx.vin[0].scriptsig; blk.extras.usd = priceUpdater.latestPrices.USD; blk.extras.medianTimestamp = block.medianTime; + blk.extras.orphans = chainTips.getOrphanedBlocksAtHeight(blk.height); if (block.height === 0) { blk.extras.medianFee = 0; // 50th percentiles @@ -204,7 +206,6 @@ class Blocks { } blk.extras.blockTime = 0; // TODO - blk.extras.orphaned = false; // TODO blk.extras.feePercentiles = await BlocksSummariesRepository.$getFeePercentilesByBlockId(block.id); if (blk.extras.feePercentiles !== null) { @@ -545,6 +546,7 @@ class Blocks { } else { this.currentBlockHeight++; logger.debug(`New block found (#${this.currentBlockHeight})!`); + await chainTips.updateOrphanedBlocks(); } const blockHash = await bitcoinApi.$getBlockHash(this.currentBlockHeight); @@ -812,6 +814,10 @@ class Blocks { } } + // Re-org can happen after indexing so we need to always get the + // latest state from core + block.orphans = chainTips.getOrphanedBlocksAtHeight(block.height); + blocks.push(block); fromHeight++; } diff --git a/backend/src/api/chain-tips.ts b/backend/src/api/chain-tips.ts new file mode 100644 index 000000000..5b0aa8a5c --- /dev/null +++ b/backend/src/api/chain-tips.ts @@ -0,0 +1,53 @@ +import logger from "../logger"; +import bitcoinClient from "./bitcoin/bitcoin-client"; + +export interface ChainTip { + height: number; + hash: string; + branchlen: number; + status: 'invalid' | 'active' | 'valid-fork' | 'valid-headers' | 'headers-only'; +}; + +export interface OrphanedBlock { + height: number; + hash: string; + status: 'valid-fork' | 'valid-headers' | 'headers-only'; +} + +class ChainTips { + private chainTips: ChainTip[] = []; + private orphanedBlocks: OrphanedBlock[] = []; + + public async updateOrphanedBlocks(): Promise { + this.chainTips = await bitcoinClient.getChainTips(); + this.orphanedBlocks = []; + + for (const chain of this.chainTips) { + if (chain.status === 'valid-fork' || chain.status === 'valid-headers' || chain.status === 'headers-only') { + let block = await bitcoinClient.getBlock(chain.hash); + while (block && block.confirmations === -1) { + this.orphanedBlocks.push({ + height: block.height, + hash: block.hash, + status: chain.status + }); + block = await bitcoinClient.getBlock(block.previousblockhash); + } + } + } + + logger.debug(`Updated orphaned blocks cache. Found ${this.orphanedBlocks.length} orphaned blocks`); + } + + public getOrphanedBlocksAtHeight(height: number): OrphanedBlock[] { + const orphans: OrphanedBlock[] = []; + for (const block of this.orphanedBlocks) { + if (block.height === height) { + orphans.push(block); + } + } + return orphans; + } +} + +export default new ChainTips(); \ No newline at end of file diff --git a/backend/src/index.ts b/backend/src/index.ts index d8d46fc9f..6ea3ddc43 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -37,6 +37,7 @@ import fundingTxFetcher from './tasks/lightning/sync-tasks/funding-tx-fetcher'; import forensicsService from './tasks/lightning/forensics.service'; import priceUpdater from './tasks/price-updater'; import mining from './api/mining/mining'; +import chainTips from './api/chain-tips'; import { AxiosError } from 'axios'; class Server { @@ -134,6 +135,7 @@ class Server { } priceUpdater.$run(); + await chainTips.updateOrphanedBlocks(); this.setUpHttpApiRoutes(); diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index a7e7c4ec6..e139bde8f 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -1,4 +1,5 @@ import { IEsploraApi } from './api/bitcoin/esplora-api.interface'; +import { OrphanedBlock } from './api/chain-tips'; import { HeapNode } from "./utils/pairing-heap"; export interface PoolTag { @@ -163,7 +164,7 @@ export interface BlockExtension { usd?: number | null; medianTimestamp?: number; blockTime?: number; - orphaned?: boolean; + orphans?: OrphanedBlock[] | null; coinbaseAddress?: string | null; coinbaseSignature?: string | null; virtualSize?: number;