Updates for general transaction and block fetching.
This commit is contained in:
parent
bb28a56622
commit
3c0fa71a10
@ -6,6 +6,7 @@ import { IEsploraApi } from './esplora-api.interface';
|
|||||||
import blocks from '../blocks';
|
import blocks from '../blocks';
|
||||||
import bitcoinBaseApi from './bitcoin-base.api';
|
import bitcoinBaseApi from './bitcoin-base.api';
|
||||||
import mempool from '../mempool';
|
import mempool from '../mempool';
|
||||||
|
import { TransactionExtended } from '../../mempool.interfaces';
|
||||||
|
|
||||||
class BitcoinApi implements AbstractBitcoinApi {
|
class BitcoinApi implements AbstractBitcoinApi {
|
||||||
private rawMempoolCache: IBitcoinApi.RawMempool | null = null;
|
private rawMempoolCache: IBitcoinApi.RawMempool | null = null;
|
||||||
@ -32,6 +33,11 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$getRawTransaction(txId: string, skipConversion = false, addPrevout = false): Promise<IEsploraApi.Transaction> {
|
$getRawTransaction(txId: string, skipConversion = false, addPrevout = false): Promise<IEsploraApi.Transaction> {
|
||||||
|
// If the transaction is in the mempool we also already fetched the fee, just prevouts are missing
|
||||||
|
const txInMempool = mempool.getMempool()[txId];
|
||||||
|
if (txInMempool && addPrevout) {
|
||||||
|
return this.$addPrevouts(txInMempool);
|
||||||
|
}
|
||||||
return this.bitcoindClient.getRawTransaction(txId, true)
|
return this.bitcoindClient.getRawTransaction(txId, true)
|
||||||
.then((transaction: IBitcoinApi.Transaction) => {
|
.then((transaction: IBitcoinApi.Transaction) => {
|
||||||
if (skipConversion) {
|
if (skipConversion) {
|
||||||
@ -55,7 +61,12 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||||||
return this.bitcoindClient.getBlockHash(height);
|
return this.bitcoindClient.getBlockHash(height);
|
||||||
}
|
}
|
||||||
|
|
||||||
$getBlock(hash: string): Promise<IEsploraApi.Block> {
|
async $getBlock(hash: string): Promise<IEsploraApi.Block> {
|
||||||
|
const foundBlock = blocks.getBlocks().find((block) => block.id === hash);
|
||||||
|
if (foundBlock) {
|
||||||
|
return foundBlock;
|
||||||
|
}
|
||||||
|
|
||||||
return this.bitcoindClient.getBlock(hash)
|
return this.bitcoindClient.getBlock(hash)
|
||||||
.then((block: IBitcoinApi.Block) => this.convertBlock(block));
|
.then((block: IBitcoinApi.Block) => this.convertBlock(block));
|
||||||
}
|
}
|
||||||
@ -163,6 +174,9 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async $appendMempoolFeeData(transaction: IEsploraApi.Transaction): Promise<IEsploraApi.Transaction> {
|
private async $appendMempoolFeeData(transaction: IEsploraApi.Transaction): Promise<IEsploraApi.Transaction> {
|
||||||
|
if (transaction.fee) {
|
||||||
|
return transaction;
|
||||||
|
}
|
||||||
let mempoolEntry: IBitcoinApi.MempoolEntry;
|
let mempoolEntry: IBitcoinApi.MempoolEntry;
|
||||||
if (!mempool.isInSync() && !this.rawMempoolCache) {
|
if (!mempool.isInSync() && !this.rawMempoolCache) {
|
||||||
this.rawMempoolCache = await bitcoinBaseApi.$getRawMempoolVerbose();
|
this.rawMempoolCache = await bitcoinBaseApi.$getRawMempoolVerbose();
|
||||||
@ -176,6 +190,17 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||||||
return transaction;
|
return transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async $addPrevouts(transaction: TransactionExtended): Promise<TransactionExtended> {
|
||||||
|
for (const vin of transaction.vin) {
|
||||||
|
if (vin.prevout) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const innerTx = await this.$getRawTransaction(vin.txid, false);
|
||||||
|
vin.prevout = innerTx.vout[vin.vout];
|
||||||
|
}
|
||||||
|
return transaction;
|
||||||
|
}
|
||||||
|
|
||||||
private async $calculateFeeFromInputs(transaction: IEsploraApi.Transaction, addPrevout: boolean): Promise<IEsploraApi.Transaction> {
|
private async $calculateFeeFromInputs(transaction: IEsploraApi.Transaction, addPrevout: boolean): Promise<IEsploraApi.Transaction> {
|
||||||
if (transaction.vin[0].is_coinbase) {
|
if (transaction.vin[0].is_coinbase) {
|
||||||
transaction.fee = 0;
|
transaction.fee = 0;
|
||||||
|
@ -8,6 +8,7 @@ import * as sha256 from 'crypto-js/sha256';
|
|||||||
import * as hexEnc from 'crypto-js/enc-hex';
|
import * as hexEnc from 'crypto-js/enc-hex';
|
||||||
import BitcoinApi from './bitcoin-api';
|
import BitcoinApi from './bitcoin-api';
|
||||||
import bitcoinBaseApi from './bitcoin-base.api';
|
import bitcoinBaseApi from './bitcoin-base.api';
|
||||||
|
import mempool from '../mempool';
|
||||||
class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
|
class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
|
||||||
private electrumClient: any;
|
private electrumClient: any;
|
||||||
|
|
||||||
@ -27,6 +28,10 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async $getRawTransaction(txId: string, skipConversion = false, addPrevout = false): Promise<IEsploraApi.Transaction> {
|
async $getRawTransaction(txId: string, skipConversion = false, addPrevout = false): Promise<IEsploraApi.Transaction> {
|
||||||
|
const txInMempool = mempool.getMempool()[txId];
|
||||||
|
if (txInMempool && addPrevout) {
|
||||||
|
return this.$addPrevouts(txInMempool);
|
||||||
|
}
|
||||||
const transaction: IBitcoinApi.Transaction = await this.electrumClient.blockchain_transaction_get(txId, true);
|
const transaction: IBitcoinApi.Transaction = await this.electrumClient.blockchain_transaction_get(txId, true);
|
||||||
if (!transaction) {
|
if (!transaction) {
|
||||||
throw new Error('Unable to get transaction: ' + txId);
|
throw new Error('Unable to get transaction: ' + txId);
|
||||||
@ -93,7 +98,7 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
|
|||||||
const history = await this.$getScriptHashHistory(addressInfo.scriptPubKey);
|
const history = await this.$getScriptHashHistory(addressInfo.scriptPubKey);
|
||||||
const transactions: IEsploraApi.Transaction[] = [];
|
const transactions: IEsploraApi.Transaction[] = [];
|
||||||
for (const h of history) {
|
for (const h of history) {
|
||||||
const tx = await this.$getRawTransaction(h.tx_hash);
|
const tx = await this.$getRawTransaction(h.tx_hash, false, true);
|
||||||
if (tx) {
|
if (tx) {
|
||||||
transactions.push(tx);
|
transactions.push(tx);
|
||||||
}
|
}
|
||||||
|
@ -64,19 +64,28 @@ class Blocks {
|
|||||||
const txIds: string[] = await bitcoinApi.$getTxIdsForBlock(blockHash);
|
const txIds: string[] = await bitcoinApi.$getTxIdsForBlock(blockHash);
|
||||||
|
|
||||||
const mempool = memPool.getMempool();
|
const mempool = memPool.getMempool();
|
||||||
let found = 0;
|
let transactionsFound = 0;
|
||||||
|
|
||||||
for (let i = 0; i < txIds.length; i++) {
|
for (let i = 0; i < txIds.length; i++) {
|
||||||
// When using bitcoind, just fetch the coinbase tx for now
|
// When using bitcoind, just fetch the coinbase tx for now
|
||||||
if (config.MEMPOOL.BACKEND !== 'none' && i === 0) {
|
if (config.MEMPOOL.BACKEND !== 'none' && i === 0) {
|
||||||
const tx = await transactionUtils.$getTransactionExtended(txIds[i]);
|
let txFound = false;
|
||||||
if (tx) {
|
let findCoinbaseTxTries = 0;
|
||||||
transactions.push(tx);
|
// It takes Electrum Server a few seconds to index the transaction after a block is found
|
||||||
|
while (findCoinbaseTxTries < 5 && !txFound) {
|
||||||
|
const tx = await transactionUtils.$getTransactionExtended(txIds[i]);
|
||||||
|
if (tx) {
|
||||||
|
txFound = true;
|
||||||
|
transactions.push(tx);
|
||||||
|
} else {
|
||||||
|
await Common.sleep(1000);
|
||||||
|
findCoinbaseTxTries++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mempool[txIds[i]]) {
|
if (mempool[txIds[i]]) {
|
||||||
transactions.push(mempool[txIds[i]]);
|
transactions.push(mempool[txIds[i]]);
|
||||||
found++;
|
transactionsFound++;
|
||||||
} else if (config.MEMPOOL.BACKEND === 'esplora') {
|
} else if (config.MEMPOOL.BACKEND === 'esplora') {
|
||||||
logger.debug(`Fetching block tx ${i} of ${txIds.length}`);
|
logger.debug(`Fetching block tx ${i} of ${txIds.length}`);
|
||||||
const tx = await transactionUtils.$getTransactionExtended(txIds[i]);
|
const tx = await transactionUtils.$getTransactionExtended(txIds[i]);
|
||||||
@ -86,11 +95,11 @@ class Blocks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(`${found} of ${txIds.length} found in mempool. ${txIds.length - found} not found.`);
|
logger.debug(`${transactionsFound} of ${txIds.length} found in mempool. ${txIds.length - transactionsFound} not found.`);
|
||||||
|
|
||||||
const blockExtended: BlockExtended = Object.assign({}, block);
|
const blockExtended: BlockExtended = Object.assign({}, block);
|
||||||
blockExtended.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
|
blockExtended.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
|
||||||
blockExtended.coinbaseTx = this.stripCoinbaseTransaction(transactions[0]);
|
blockExtended.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
|
||||||
transactions.sort((a, b) => b.feePerVsize - a.feePerVsize);
|
transactions.sort((a, b) => b.feePerVsize - a.feePerVsize);
|
||||||
blockExtended.medianFee = transactions.length > 1 ? Common.median(transactions.map((tx) => tx.feePerVsize)) : 0;
|
blockExtended.medianFee = transactions.length > 1 ? Common.median(transactions.map((tx) => tx.feePerVsize)) : 0;
|
||||||
blockExtended.feeRange = transactions.length > 1 ? Common.getFeesInRange(transactions.slice(0, transactions.length - 1), 8) : [0, 0];
|
blockExtended.feeRange = transactions.length > 1 ? Common.getFeesInRange(transactions.slice(0, transactions.length - 1), 8) : [0, 0];
|
||||||
@ -118,20 +127,6 @@ class Blocks {
|
|||||||
public getCurrentBlockHeight(): number {
|
public getCurrentBlockHeight(): number {
|
||||||
return this.currentBlockHeight;
|
return this.currentBlockHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
private 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)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new Blocks();
|
export default new Blocks();
|
||||||
|
@ -56,4 +56,12 @@ export class Common {
|
|||||||
value: tx.vout.reduce((acc, vout) => acc + (vout.value ? vout.value : 0), 0),
|
value: tx.vout.reduce((acc, vout) => acc + (vout.value ? vout.value : 0), 0),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static sleep(ms: number): Promise<void> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve();
|
||||||
|
}, ms);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,27 +6,6 @@ import { IEsploraApi } from './bitcoin/esplora-api.interface';
|
|||||||
class TransactionUtils {
|
class TransactionUtils {
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
public async $addPrevoutsToTransaction(transaction: TransactionExtended): Promise<TransactionExtended> {
|
|
||||||
if (transaction.vin[0].is_coinbase) {
|
|
||||||
return transaction;
|
|
||||||
}
|
|
||||||
for (const vin of transaction.vin) {
|
|
||||||
const innerTx = await bitcoinApi.$getRawTransaction(vin.txid);
|
|
||||||
vin.prevout = innerTx.vout[vin.vout];
|
|
||||||
}
|
|
||||||
return transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
public extendTransaction(transaction: IEsploraApi.Transaction): TransactionExtended {
|
|
||||||
transaction['vsize'] = Math.round(transaction.weight / 4);
|
|
||||||
transaction['feePerVsize'] = Math.max(1, (transaction.fee || 0) / (transaction.weight / 4));
|
|
||||||
if (!transaction.status.confirmed) {
|
|
||||||
transaction['firstSeen'] = Math.round((new Date().getTime() / 1000));
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
return transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
public stripCoinbaseTransaction(tx: TransactionExtended): TransactionMinerInfo {
|
public stripCoinbaseTransaction(tx: TransactionExtended): TransactionMinerInfo {
|
||||||
return {
|
return {
|
||||||
vin: [{
|
vin: [{
|
||||||
@ -41,10 +20,10 @@ class TransactionUtils {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $getTransactionExtended(txId: string, inMempool = false, addPrevouts = false): Promise<TransactionExtended | null> {
|
public async $getTransactionExtended(txId: string, forceBitcoind = false, addPrevouts = false): Promise<TransactionExtended | null> {
|
||||||
try {
|
try {
|
||||||
let transaction: IEsploraApi.Transaction;
|
let transaction: IEsploraApi.Transaction;
|
||||||
if (inMempool) {
|
if (forceBitcoind) {
|
||||||
transaction = await bitcoinApi.$getRawTransactionBitcoind(txId, false, addPrevouts);
|
transaction = await bitcoinApi.$getRawTransactionBitcoind(txId, false, addPrevouts);
|
||||||
} else {
|
} else {
|
||||||
transaction = await bitcoinApi.$getRawTransaction(txId, false, addPrevouts);
|
transaction = await bitcoinApi.$getRawTransaction(txId, false, addPrevouts);
|
||||||
@ -52,11 +31,20 @@ class TransactionUtils {
|
|||||||
return this.extendTransaction(transaction);
|
return this.extendTransaction(transaction);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.debug('getTransactionExtended error: ' + (e.message || e));
|
logger.debug('getTransactionExtended error: ' + (e.message || e));
|
||||||
console.log(e);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extendTransaction(transaction: IEsploraApi.Transaction): TransactionExtended {
|
||||||
|
const transactionExtended: TransactionExtended = Object.assign({
|
||||||
|
vsize: Math.round(transaction.weight / 4),
|
||||||
|
feePerVsize: Math.max(1, (transaction.fee || 0) / (transaction.weight / 4)),
|
||||||
|
}, transaction);
|
||||||
|
if (!transaction.status.confirmed) {
|
||||||
|
transactionExtended.firstSeen = Math.round((new Date().getTime() / 1000));
|
||||||
|
}
|
||||||
|
return transactionExtended;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new TransactionUtils();
|
export default new TransactionUtils();
|
||||||
|
@ -528,13 +528,7 @@ class Routes {
|
|||||||
|
|
||||||
public async getTransaction(req: Request, res: Response) {
|
public async getTransaction(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
let transaction: TransactionExtended | null;
|
const transaction = await transactionUtils.$getTransactionExtended(req.params.txId, false, true);
|
||||||
const txInMempool = mempool.getMempool()[req.params.txId];
|
|
||||||
if (txInMempool) {
|
|
||||||
transaction = txInMempool;
|
|
||||||
} else {
|
|
||||||
transaction = await transactionUtils.$getTransactionExtended(req.params.txId, false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transaction) {
|
if (transaction) {
|
||||||
res.json(transaction);
|
res.json(transaction);
|
||||||
@ -563,8 +557,9 @@ class Routes {
|
|||||||
try {
|
try {
|
||||||
const txIds = await bitcoinApi.$getTxIdsForBlock(req.params.hash);
|
const txIds = await bitcoinApi.$getTxIdsForBlock(req.params.hash);
|
||||||
const transactions: TransactionExtended[] = [];
|
const transactions: TransactionExtended[] = [];
|
||||||
|
const startingIndex = Math.max(0, parseInt(req.params.index, 10));
|
||||||
|
|
||||||
for (let i = 0; i < Math.min(15, txIds.length); i++) {
|
for (let i = startingIndex; i < Math.min(startingIndex + 10, txIds.length); i++) {
|
||||||
const transaction = await transactionUtils.$getTransactionExtended(txIds[i], false, true);
|
const transaction = await transactionUtils.$getTransactionExtended(txIds[i], false, true);
|
||||||
if (transaction) {
|
if (transaction) {
|
||||||
transactions.push(transaction);
|
transactions.push(transaction);
|
||||||
@ -577,7 +572,12 @@ class Routes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getBlockHeight(req: Request, res: Response) {
|
public async getBlockHeight(req: Request, res: Response) {
|
||||||
res.status(404).send('Not implemented');
|
try {
|
||||||
|
const blockHash = await bitcoinApi.$getBlockHash(parseInt(req.params.height, 10));
|
||||||
|
res.send(blockHash);
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).send(e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAddress(req: Request, res: Response) {
|
public async getAddress(req: Request, res: Response) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user