UNPKG

bitcore-node

Version:

A blockchain indexing node with extended capabilities using bitcore

222 lines 10.2 kB
#!/usr/bin/env node "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const stream_1 = require("stream"); const block_1 = require("../../src/models/block"); const coin_1 = require("../../src/models/coin"); const transaction_1 = require("../../src/models/transaction"); const modules_1 = require("../../src/modules"); const config_1 = require("../../src/services/config"); const storage_1 = require("../../src/services/storage"); const verification_1 = require("../../src/services/verification"); (async () => { const { CHAIN, NETWORK, FILE, DRYRUN = true } = process.env; if (!CHAIN || !NETWORK || !FILE) { console.log('CHAIN, NETWORK, and FILE env variable are required'); process.exit(1); } const dry = DRYRUN && DRYRUN !== 'false'; const chain = CHAIN || ''; const network = NETWORK || ''; await storage_1.Storage.start(); modules_1.Modules.loadConfigured(); const chainConfig = config_1.Config.chainConfig({ chain, network }); const workerClass = verification_1.Verification.get(chain, network); const worker = new workerClass({ chain, network, chainConfig }); await worker.connect(); const handleRepair = async (data) => { try { const tip = await block_1.BitcoinBlockStorage.getLocalTip({ chain, network }); switch (data.type) { case 'DUPE_TRANSACTION': { const tx = data.payload.tx; const dupeTxs = await transaction_1.TransactionStorage.collection .find({ chain: tx.chain, network: tx.network, txid: tx.txid }) .sort({ blockHeight: -1 }) .toArray(); if (dupeTxs.length < 2) { console.log('No action required.', dupeTxs.length, 'transaction'); return; } let toKeep = dupeTxs[0]; const wouldBeDeleted = dupeTxs.filter(c => c._id != toKeep._id); if (dry) { console.log('WOULD DELETE'); console.log(wouldBeDeleted); } else { console.log('Deleting', wouldBeDeleted.length, 'transactions'); await transaction_1.TransactionStorage.collection.deleteMany({ chain, network, _id: { $in: wouldBeDeleted.map(c => c._id) } }); } } break; case 'FORK_PRUNE_COIN': { const coin = data.payload.coin; const forkCoins = await coin_1.CoinStorage.collection .find({ chain, network, mintTxid: coin.mintTxid, mintIndex: coin.mintIndex, spentHeight: -2 }) .sort({ mintHeight: -1, spentHeight: -1 }) .toArray(); if (forkCoins.length === 0) { console.log('No action required. Coin already pruned'); return; } const wouldBeDeleted = forkCoins; if (dry) { console.log('WOULD DELETE'); console.log(wouldBeDeleted); } else { console.log('Deleting', wouldBeDeleted.length, 'coins'); await coin_1.CoinStorage.collection.deleteMany({ chain, network, _id: { $in: wouldBeDeleted.map(c => c._id) } }); } } break; case 'DUPE_COIN': { const coin = data.payload.coin; const dupeCoins = await coin_1.CoinStorage.collection .find({ chain, network, mintTxid: coin.mintTxid, mintIndex: coin.mintIndex }) .sort({ mintHeight: -1, spentHeight: -1 }) .toArray(); if (dupeCoins.length < 2) { console.log('No action required.', dupeCoins.length, 'coin'); return; } let toKeep = dupeCoins[0]; const spentCoin = dupeCoins.find(c => c.spentHeight > toKeep.spentHeight); toKeep = spentCoin || toKeep; const wouldBeDeleted = dupeCoins.filter(c => c._id != toKeep._id); if (dry) { console.log('WOULD DELETE'); console.log(wouldBeDeleted); } else { const { mintIndex, mintTxid } = toKeep; console.log('Deleting', wouldBeDeleted.length, 'coins'); await coin_1.CoinStorage.collection.deleteMany({ chain, network, mintTxid, mintIndex, _id: { $in: wouldBeDeleted.map(c => c._id) } }); } } break; case 'COIN_HEIGHT_MISMATCH': case 'CORRUPTED_BLOCK': case 'MISSING_BLOCK': case 'MISSING_TX': case 'MISSING_COIN_FOR_TXID': case 'VALUE_MISMATCH': case 'COIN_SHOULD_BE_SPENT': case 'NEG_FEE': const blockHeight = Number(data.payload.blockNum); let { success } = await worker.validateDataForBlock(blockHeight, tip.height); if (success) { console.log('No errors found, repaired previously'); return; } if (dry) { console.log('WOULD RESYNC BLOCKS', blockHeight, 'to', blockHeight + 1); console.log(data.payload); } else { console.log('Resyncing Blocks', blockHeight, 'to', blockHeight + 1); await worker.resync(blockHeight - 1, blockHeight + 1); let { success, errors } = await worker.validateDataForBlock(blockHeight, tip.height); if (success) { console.log('REPAIR SOLVED ISSUE'); } else { console.log('REPAIR FAILED TO SOLVE ISSUE'); console.log(JSON.stringify(errors, null, 2)); } } break; case 'DUPE_BLOCKHEIGHT': case 'DUPE_BLOCKHASH': const dupeBlock = await block_1.BitcoinBlockStorage.collection .find({ chain, network, height: data.payload.blockNum }) .toArray(); if (dupeBlock.length < 2) { console.log('No action required.', dupeBlock.length, 'block'); return; } let toKeepBlock = dupeBlock[0]; const processedBlock = dupeBlock.find(b => b.processed === true); toKeepBlock = processedBlock || toKeepBlock; const wouldBeDeletedBlock = dupeBlock.filter(c => c._id !== toKeepBlock._id); if (dry) { console.log('WOULD DELETE'); console.log(wouldBeDeletedBlock); } else { console.log('Deleting', wouldBeDeletedBlock.length, 'block'); await block_1.BitcoinBlockStorage.collection.deleteMany({ chain, network, _id: { $in: wouldBeDeletedBlock.map(c => c._id) } }); } break; default: console.log('skipping'); } } catch (e) { console.error(e); } }; function getLinesFromChunk(chunk) { return chunk.toString().split('\n'); } async function repairLineIfValidJson(line) { const dataStr = line.trim(); if (dataStr && dataStr.length > 2) { if (dataStr.startsWith('{') && dataStr.endsWith('}')) { try { const parsedData = JSON.parse(line); console.log('Inspecting...'); console.log(dataStr); await handleRepair(parsedData); } catch (err) { } } } } async function transformFileChunks(chunk, _, cb) { for (let line of getLinesFromChunk(chunk)) { await repairLineIfValidJson(line); } cb(); } const getFileContents = FILE => { fs_1.default.createReadStream(FILE) .pipe(new stream_1.Transform({ write: transformFileChunks })) .on('end', () => { process.exit(0); }) .on('finish', () => { process.exit(0); }); }; getFileContents(FILE); })(); //# sourceMappingURL=db-repair.js.map