Skip to content

Commit

Permalink
Merge branch 'fixLongInvalidChains' of github.com:kajoseph/bitcore
Browse files Browse the repository at this point in the history
  • Loading branch information
kajoseph committed Sep 27, 2024
2 parents 235fae7 + fd1ff66 commit db772da
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 163 deletions.
73 changes: 73 additions & 0 deletions packages/bitcore-node/scripts/fixReplacedTree.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env node

const CWC = require('crypto-wallet-core');
const { Storage } = require('../build/src/services/storage');
const { TransactionStorage } = require('../build/src/models/transaction');

function usage(errMsg) {
console.log('USAGE: ./fixreplacedTree.js <txid> <replacementTxid> [options]');
console.log('OPTIONS:');
console.log(' --chain <value> BTC, BCH, DOGE, or LTC');
console.log(' --network <value> mainnet, testnet, or regtest');
console.log(' --real Write the change to the db. If not given, will only do a dry run');
console.log(' --force Force the overwrite of an existing replacedByTxid field');
if (errMsg) {
console.log('\nERROR: ' + errMsg);
}
process.exit();
}

const args = process.argv.slice(2);

if (args.includes('--help') || args.includes('-h')) {
usage();
}

if (!args[0] || !/^[a-f0-9]{64}$/i.test(args[0])) {
usage('Missing or invalid txid param.');
}

if (!args[1] || !/^[a-f0-9]{64}$/i.test(args[1])) {
usage('Missing replacementTxid param.');
}

const [txid, replacementTxid] = args;
const chain = args[args.indexOf('--chain') + 1];
const network = args[args.indexOf('--network') + 1];

if (!['BTC', 'BCH', 'DOGE', 'LTC'].includes(chain) || !['mainnet', 'testnet', 'regtest'].includes(network)) {
usage('Invalid chain and/or network param(s).');
}

const real = !!args.find(a => a === '--real');
const force = !!args.find(a => a === '--force');

real && console.log('~~~~ DRY RUN ~~~~');
console.log('Connecting to storage...');
Storage.start()
.then(async () => {
const tx = await TransactionStorage.collection.findOne({ chain, network, txid });

if (!tx) {
console.log('No tx found for txid.');
return;
}

if (tx.replacedByTxid) {
console.log('Tx already has replacement txid: ' + tx.replacedByTxid);
if (!force) {
return;
}
}

if (real) {
await TransactionStorage._invalidateTx({ chain, network, invalidTxid: txid, replacedByTxid: replacementTxid });

const tx = await TransactionStorage.collection.findOne({ chain, network, txid });
console.log('Tx replaced', tx.replacedByTxid);
} else {
console.log('Dry run complete.');
}
})
.catch(console.error)
.finally(Storage.stop.bind(Storage))
46 changes: 25 additions & 21 deletions packages/bitcore-node/src/models/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -652,15 +652,15 @@ export class TransactionModel extends BaseTransaction<IBtcTransaction> {
spentTxid: { $ne: minedTxid }
};

const conflictingInputsStream = await CoinStorage.collection.find(conflictingInputsQuery);
const conflictingInputsStream = CoinStorage.collection.find(conflictingInputsQuery);
const seenInvalidTxids = new Set();
let input: ICoin | null;

while ((input = await conflictingInputsStream.next())) {
if (seenInvalidTxids.has(input.spentTxid)) {
continue;
}
await this._invalidateTx({ chain, network, invalidTxid: input.spentTxid, replacedByTxid: minedTxid });
await this._invalidateTx({ chain, network, invalidTxid: input.spentTxid, replacedByTxid: minedTxid, simple: true });
seenInvalidTxids.add(input.spentTxid);
}

Expand All @@ -673,29 +673,33 @@ export class TransactionModel extends BaseTransaction<IBtcTransaction> {
async _invalidateTx(params: {
chain: string;
network: string;
invalidTxid: string,
replacedByTxid?: string // only provided at the beginning of the ancestral tree. Txs that spend unconfirmed outputs aren't "replaced"
invalidParentTxids?: string[] // empty at the beginning of the ancestral tree.
invalidTxid: string;
replacedByTxid?: string; // only provided at the beginning of the ancestral tree. Txs that spend unconfirmed outputs aren't "replaced"
invalidParentTxids?: string[]; // empty at the beginning of the ancestral tree.
simple?: boolean; // if true, don't invalidate descendants
}) {
const { chain, network, invalidTxid, replacedByTxid, invalidParentTxids = [] } = params;
const spentOutputsQuery = {
chain,
network,
spentHeight: SpentHeightIndicators.pending,
mintTxid: invalidTxid
};
const { chain, network, invalidTxid, replacedByTxid, invalidParentTxids = [], simple } = params;

if (!simple) {
const spentOutputsQuery = {
chain,
network,
spentHeight: SpentHeightIndicators.pending,
mintTxid: invalidTxid
};

// spent outputs of invalid tx
const spentOutputsStream = await CoinStorage.collection.find(spentOutputsQuery);
const seenTxids = new Set();
let output: ICoin | null;
// spent outputs of invalid tx
const spentOutputsStream = CoinStorage.collection.find(spentOutputsQuery);
const seenTxids = new Set();
let output: ICoin | null;

while ((output = await spentOutputsStream.next())) {
if (!output.spentTxid || seenTxids.has(output.spentTxid)) {
continue;
while ((output = await spentOutputsStream.next())) {
if (!output.spentTxid || seenTxids.has(output.spentTxid)) {
continue;
}
// invalidate descendent tx (tx spending unconfirmed UTXO)
await this._invalidateTx({ chain, network, invalidTxid: output.spentTxid, invalidParentTxids: [...invalidParentTxids, invalidTxid], simple });
}
// invalidate descendent tx (tx spending unconfirmed UTXO)
await this._invalidateTx({ chain, network, invalidTxid: output.spentTxid, invalidParentTxids: [...invalidParentTxids, invalidTxid] });
}

const setTx: { blockHeight: number; replacedByTxid?: string } = { blockHeight: SpentHeightIndicators.conflicting };
Expand Down
8 changes: 4 additions & 4 deletions packages/bitcore-node/src/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,17 @@ export class RPC {
return this.asyncCall('getblockcount', []);
}

getBlock(hash: string, verbose: boolean) {
return this.asyncCall<RPCBlock<RPCTransaction>>('getblock', [hash, verbose]);
getBlock(hash: string, verbosity: number) {
return this.asyncCall<RPCBlock<RPCTransaction>>('getblock', [hash, verbosity]);
}

getBlockHash(height: number) {
return this.asyncCall<string>('getblockhash', [height]);
}

async getBlockByHeight(height: number) {
async getBlockByHeight(height: number, verbosity: number) {
const hash = await this.getBlockHash(height);
return this.getBlock(hash, false);
return this.getBlock(hash, verbosity);
}

getTransaction(txid: string) {
Expand Down
Loading

0 comments on commit db772da

Please sign in to comment.