Refactored transaction handling.
This commit is contained in:
124
backend/src/api/transaction-utils.ts
Normal file
124
backend/src/api/transaction-utils.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import bitcoinApi from './bitcoin/bitcoin-api-factory';
|
||||
import { MempoolEntries, MempoolEntry, Transaction, TransactionExtended, TransactionMinerInfo } from '../interfaces';
|
||||
import config from '../config';
|
||||
import logger from '../logger';
|
||||
import mempool from './mempool';
|
||||
import blocks from './blocks';
|
||||
|
||||
class TransactionUtils {
|
||||
private mempoolEntriesCache: MempoolEntries | null = null;
|
||||
|
||||
constructor() { }
|
||||
|
||||
public async $addPrevoutsToTransaction(transaction: TransactionExtended): Promise<TransactionExtended> {
|
||||
for (const vin of transaction.vin) {
|
||||
const innerTx = await bitcoinApi.$getRawTransaction(vin.txid);
|
||||
vin.prevout = innerTx.vout[vin.vout];
|
||||
}
|
||||
return transaction;
|
||||
}
|
||||
|
||||
public async $calculateFeeFromInputs(transaction: Transaction): Promise<TransactionExtended> {
|
||||
if (transaction.vin[0]['coinbase']) {
|
||||
transaction.fee = 0;
|
||||
// @ts-ignore
|
||||
return transaction;
|
||||
}
|
||||
let totalIn = 0;
|
||||
for (const vin of transaction.vin) {
|
||||
const innerTx = await bitcoinApi.$getRawTransaction(vin.txid);
|
||||
totalIn += innerTx.vout[vin.vout].value;
|
||||
}
|
||||
const totalOut = transaction.vout.reduce((prev, output) => prev + output.value, 0);
|
||||
transaction.fee = parseFloat((totalIn - totalOut).toFixed(8));
|
||||
return this.extendTransaction(transaction);
|
||||
}
|
||||
|
||||
public extendTransaction(transaction: Transaction | MempoolEntry): TransactionExtended {
|
||||
// @ts-ignore
|
||||
return Object.assign({
|
||||
vsize: Math.round(transaction.weight / 4),
|
||||
feePerVsize: Math.max(1, (transaction.fee || 0) / (transaction.weight / 4)),
|
||||
firstSeen: Math.round((new Date().getTime() / 1000)),
|
||||
}, transaction);
|
||||
}
|
||||
|
||||
public stripCoinbaseTransaction(tx: TransactionExtended): TransactionMinerInfo {
|
||||
return {
|
||||
vin: [{
|
||||
scriptsig: tx.vin[0].scriptsig || tx.vin[0]['coinbase']
|
||||
}],
|
||||
vout: tx.vout
|
||||
.map((vout) => ({
|
||||
scriptpubkey_address: vout.scriptpubkey_address,
|
||||
value: vout.value
|
||||
}))
|
||||
.filter((vout) => vout.value)
|
||||
};
|
||||
}
|
||||
|
||||
public async getTransactionExtended(txId: string, isCoinbase = false, inMempool = false): Promise<TransactionExtended | null> {
|
||||
try {
|
||||
let transaction: Transaction;
|
||||
if (inMempool) {
|
||||
transaction = await bitcoinApi.$getRawTransactionBitcond(txId);
|
||||
} else {
|
||||
transaction = await bitcoinApi.$getRawTransaction(txId);
|
||||
}
|
||||
if (config.MEMPOOL.BACKEND !== 'electrs' && !isCoinbase) {
|
||||
if (inMempool) {
|
||||
transaction = await this.$appendFeeData(transaction);
|
||||
} else {
|
||||
transaction = await this.$calculateFeeFromInputs(transaction);
|
||||
}
|
||||
}
|
||||
return this.extendTransaction(transaction);
|
||||
} catch (e) {
|
||||
logger.debug('getTransactionExtended error: ' + (e.message || e));
|
||||
console.log(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public bitcoindToElectrsTransaction(transaction: any): void {
|
||||
try {
|
||||
transaction.vout = transaction.vout.map((vout) => {
|
||||
return {
|
||||
value: vout.value * 100000000,
|
||||
scriptpubkey: vout.scriptPubKey.hex,
|
||||
scriptpubkey_address: vout.scriptPubKey && vout.scriptPubKey.addresses ? vout.scriptPubKey.addresses[0] : null,
|
||||
scriptpubkey_asm: vout.scriptPubKey.asm,
|
||||
scriptpubkey_type: vout.scriptPubKey.type,
|
||||
};
|
||||
});
|
||||
if (transaction.confirmations) {
|
||||
transaction['status'] = {
|
||||
confirmed: true,
|
||||
block_height: blocks.getCurrentBlockHeight() - transaction.confirmations,
|
||||
block_hash: transaction.blockhash,
|
||||
block_time: transaction.blocktime,
|
||||
};
|
||||
} else {
|
||||
transaction['status'] = { confirmed: false };
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('augment failed: ' + (e.message || e));
|
||||
}
|
||||
}
|
||||
|
||||
private async $appendFeeData(transaction: Transaction): Promise<Transaction> {
|
||||
let mempoolEntry: MempoolEntry;
|
||||
if (!mempool.isInSync() && !this.mempoolEntriesCache) {
|
||||
this.mempoolEntriesCache = await bitcoinApi.$getRawMempoolVerbose();
|
||||
}
|
||||
if (this.mempoolEntriesCache && this.mempoolEntriesCache[transaction.txid]) {
|
||||
mempoolEntry = this.mempoolEntriesCache[transaction.txid];
|
||||
} else {
|
||||
mempoolEntry = await bitcoinApi.$getMempoolEntry(transaction.txid);
|
||||
}
|
||||
transaction.fee = mempoolEntry.fees.base * 100000000;
|
||||
return transaction;
|
||||
}
|
||||
}
|
||||
|
||||
export default new TransactionUtils();
|
||||
Reference in New Issue
Block a user