bitcore-node
Version:
A blockchain indexing node with extended capabilities using bitcore
222 lines • 10.2 kB
JavaScript
#!/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