standardize API error strings & validation

This commit is contained in:
Mononaut
2024-12-19 12:50:17 +00:00
parent 01df22ef86
commit 47044db043
10 changed files with 284 additions and 169 deletions

View File

@@ -3,6 +3,10 @@ import logger from '../../logger';
import bitcoinClient from './bitcoin-client';
import config from '../../config';
const BLOCKHASH_REGEX = /^[a-f0-9]{64}$/i;
const TXID_REGEX = /^[a-f0-9]{64}$/i;
const RAW_TX_REGEX = /^[a-f0-9]{2,}$/i;
/**
* Define a set of routes used by the accelerator server
* Those routes are not designed to be public
@@ -10,7 +14,7 @@ import config from '../../config';
class BitcoinBackendRoutes {
private static tag = 'BitcoinBackendRoutes';
public initRoutes(app: Application) {
public initRoutes(app: Application): void {
app
.get(config.MEMPOOL.API_URL_PREFIX + 'internal/bitcoin-core/' + 'get-mempool-entry', this.disableCache, this.$getMempoolEntry)
.post(config.MEMPOOL.API_URL_PREFIX + 'internal/bitcoin-core/' + 'decode-raw-transaction', this.disableCache, this.$decodeRawTransaction)
@@ -26,10 +30,10 @@ class BitcoinBackendRoutes {
/**
* Disable caching for bitcoin core routes
*
* @param req
* @param res
* @param next
*
* @param req
* @param res
* @param next
*/
private disableCache(req: Request, res: Response, next: NextFunction): void {
res.setHeader('Pragma', 'no-cache');
@@ -40,16 +44,16 @@ class BitcoinBackendRoutes {
/**
* Exeption handler to return proper details to the accelerator server
*
* @param e
* @param fnName
* @param res
*
* @param e
* @param fnName
* @param res
*/
private static handleException(e: any, fnName: string, res: Response): void {
if (typeof(e.code) === 'number') {
res.status(400).send(JSON.stringify(e, ['code', 'message']));
} else {
const err = `exception in ${fnName}. ${e}. Details: ${JSON.stringify(e, ['code', 'message'])}`;
res.status(400).send(JSON.stringify(e, ['code']));
} else {
const err = `unknown exception in ${fnName}`;
logger.err(err, BitcoinBackendRoutes.tag);
res.status(500).send(err);
}
@@ -58,13 +62,13 @@ class BitcoinBackendRoutes {
private async $getMempoolEntry(req: Request, res: Response): Promise<void> {
const txid = req.query.txid;
try {
if (typeof(txid) !== 'string' || txid.length !== 64) {
res.status(400).send(`invalid param txid ${txid}. must be a string of 64 char`);
if (typeof(txid) !== 'string' || txid.length !== 64 || !TXID_REGEX.test(txid)) {
res.status(400).send(`invalid param txid. must be 64 hexadecimal characters`);
return;
}
const mempoolEntry = await bitcoinClient.getMempoolEntry(txid);
if (!mempoolEntry) {
res.status(404).send(`no mempool entry found for txid ${txid}`);
res.status(404).send();
return;
}
res.status(200).send(mempoolEntry);
@@ -76,13 +80,13 @@ class BitcoinBackendRoutes {
private async $decodeRawTransaction(req: Request, res: Response): Promise<void> {
const rawTx = req.body.rawTx;
try {
if (typeof(rawTx) !== 'string') {
res.status(400).send(`invalid param rawTx ${rawTx}. must be a string`);
if (typeof(rawTx) !== 'string' || !RAW_TX_REGEX.test(rawTx)) {
res.status(400).send(`invalid param rawTx. must be a string of hexadecimal characters`);
return;
}
const decodedTx = await bitcoinClient.decodeRawTransaction(rawTx);
if (!decodedTx) {
res.status(400).send(`unable to decode rawTx ${rawTx}`);
res.status(400).send(`unable to decode rawTx`);
return;
}
res.status(200).send(decodedTx);
@@ -95,23 +99,23 @@ class BitcoinBackendRoutes {
const txid = req.query.txid;
const verbose = req.query.verbose;
try {
if (typeof(txid) !== 'string' || txid.length !== 64) {
res.status(400).send(`invalid param txid ${txid}. must be a string of 64 char`);
if (typeof(txid) !== 'string' || txid.length !== 64 || !TXID_REGEX.test(txid)) {
res.status(400).send(`invalid param txid. must be 64 hexadecimal characters`);
return;
}
if (typeof(verbose) !== 'string') {
res.status(400).send(`invalid param verbose ${verbose}. must be a string representing an integer`);
res.status(400).send(`invalid param verbose. must be a string representing an integer`);
return;
}
const verboseNumber = parseInt(verbose, 10);
if (typeof(verboseNumber) !== 'number') {
res.status(400).send(`invalid param verbose ${verbose}. must be a valid integer`);
res.status(400).send(`invalid param verbose. must be a valid integer`);
return;
}
const decodedTx = await bitcoinClient.getRawTransaction(txid, verboseNumber);
if (!decodedTx) {
res.status(400).send(`unable to get raw transaction for txid ${txid}`);
res.status(400).send(`unable to get raw transaction`);
return;
}
res.status(200).send(decodedTx);
@@ -123,13 +127,13 @@ class BitcoinBackendRoutes {
private async $sendRawTransaction(req: Request, res: Response): Promise<void> {
const rawTx = req.body.rawTx;
try {
if (typeof(rawTx) !== 'string') {
res.status(400).send(`invalid param rawTx ${rawTx}. must be a string`);
if (typeof(rawTx) !== 'string' || !RAW_TX_REGEX.test(rawTx)) {
res.status(400).send(`invalid param rawTx. must be a string of hexadecimal characters`);
return;
}
const txHex = await bitcoinClient.sendRawTransaction(rawTx);
if (!txHex) {
res.status(400).send(`unable to send rawTx ${rawTx}`);
res.status(400).send(`unable to send rawTx`);
return;
}
res.status(200).send(txHex);
@@ -141,13 +145,13 @@ class BitcoinBackendRoutes {
private async $testMempoolAccept(req: Request, res: Response): Promise<void> {
const rawTxs = req.body.rawTxs;
try {
if (typeof(rawTxs) !== 'object') {
res.status(400).send(`invalid param rawTxs ${JSON.stringify(rawTxs)}. must be an array of string`);
if (typeof(rawTxs) !== 'object' || !Array.isArray(rawTxs) || rawTxs.some((tx) => typeof(tx) !== 'string' || !RAW_TX_REGEX.test(tx))) {
res.status(400).send(`invalid param rawTxs. must be an array of strings of hexadecimal characters`);
return;
}
const txHex = await bitcoinClient.testMempoolAccept(rawTxs);
if (typeof(txHex) !== 'object' || txHex.length === 0) {
res.status(400).send(`testmempoolaccept failed for raw txs ${JSON.stringify(rawTxs)}, got an empty result`);
res.status(400).send(`testmempoolaccept failed for raw txs, got an empty result`);
return;
}
res.status(200).send(txHex);
@@ -160,18 +164,18 @@ class BitcoinBackendRoutes {
const txid = req.query.txid;
const verbose = req.query.verbose;
try {
if (typeof(txid) !== 'string' || txid.length !== 64) {
res.status(400).send(`invalid param txid ${txid}. must be a string of 64 char`);
if (typeof(txid) !== 'string' || txid.length !== 64 || !TXID_REGEX.test(txid)) {
res.status(400).send(`invalid param txid. must be 64 hexadecimal characters`);
return;
}
if (typeof(verbose) !== 'string' || (verbose !== 'true' && verbose !== 'false')) {
res.status(400).send(`invalid param verbose ${verbose}. must be a string ('true' | 'false')`);
res.status(400).send(`invalid param verbose. must be a string ('true' | 'false')`);
return;
}
const ancestors = await bitcoinClient.getMempoolAncestors(txid, verbose === 'true' ? true : false);
if (!ancestors) {
res.status(400).send(`unable to get mempool ancestors for txid ${txid}`);
res.status(400).send(`unable to get mempool ancestors`);
return;
}
res.status(200).send(ancestors);
@@ -184,23 +188,23 @@ class BitcoinBackendRoutes {
const blockHash = req.query.hash;
const verbosity = req.query.verbosity;
try {
if (typeof(blockHash) !== 'string' || blockHash.length !== 64) {
res.status(400).send(`invalid param blockHash ${blockHash}. must be a string of 64 char`);
if (typeof(blockHash) !== 'string' || blockHash.length !== 64 || !BLOCKHASH_REGEX.test(blockHash)) {
res.status(400).send(`invalid param blockHash. must be 64 hexadecimal characters`);
return;
}
if (typeof(verbosity) !== 'string') {
res.status(400).send(`invalid param verbosity ${verbosity}. must be a string representing an integer`);
res.status(400).send(`invalid param verbosity. must be a string representing an integer`);
return;
}
const verbosityNumber = parseInt(verbosity, 10);
if (typeof(verbosityNumber) !== 'number') {
res.status(400).send(`invalid param verbosity ${verbosity}. must be a valid integer`);
res.status(400).send(`invalid param verbosity. must be a valid integer`);
return;
}
const block = await bitcoinClient.getBlock(blockHash, verbosityNumber);
if (!block) {
res.status(400).send(`unable to get block for block hash ${blockHash}`);
res.status(400).send(`unable to get block`);
return;
}
res.status(200).send(block);
@@ -213,18 +217,18 @@ class BitcoinBackendRoutes {
const blockHeight = req.query.height;
try {
if (typeof(blockHeight) !== 'string') {
res.status(400).send(`invalid param blockHeight ${blockHeight}, must be a string representing an integer`);
res.status(400).send(`invalid param blockHeight, must be a string representing an integer`);
return;
}
const blockHeightNumber = parseInt(blockHeight, 10);
if (typeof(blockHeightNumber) !== 'number') {
res.status(400).send(`invalid param blockHeight ${blockHeight}. must be a valid integer`);
res.status(400).send(`invalid param blockHeight. must be a valid integer`);
return;
}
const block = await bitcoinClient.getBlockHash(blockHeightNumber);
if (!block) {
res.status(400).send(`unable to get block hash for block height ${blockHeightNumber}`);
res.status(400).send(`unable to get block hash`);
return;
}
res.status(200).send(block);
@@ -247,4 +251,4 @@ class BitcoinBackendRoutes {
}
}
export default new BitcoinBackendRoutes
export default new BitcoinBackendRoutes;

View File

@@ -22,6 +22,11 @@ import rbfCache from '../rbf-cache';
import { calculateMempoolTxCpfp } from '../cpfp';
import { handleError } from '../../utils/api';
const TXID_REGEX = /^[a-f0-9]{64}$/i;
const BLOCK_HASH_REGEX = /^[a-f0-9]{64}$/i;
const ADDRESS_REGEX = /^[a-z0-9]{2,120}$/i;
const SCRIPT_HASH_REGEX = /^([a-f0-9]{2})+$/i;
class BitcoinRoutes {
public initRoutes(app: Application) {
app
@@ -90,7 +95,7 @@ class BitcoinRoutes {
res.set('Content-Type', 'application/json');
res.send(result);
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get init data');
}
}
@@ -109,7 +114,7 @@ class BitcoinRoutes {
const result = mempoolBlocks.getMempoolBlocks();
res.json(result);
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get mempool blocks');
}
}
@@ -121,7 +126,10 @@ class BitcoinRoutes {
const txIds: string[] = [];
for (const _txId in req.query.txId) {
if (typeof req.query.txId[_txId] === 'string') {
txIds.push(req.query.txId[_txId].toString());
const txid = req.query.txId[_txId].toString();
if (TXID_REGEX.test(txid)) {
txIds.push(txid);
}
}
}
@@ -140,18 +148,22 @@ class BitcoinRoutes {
handleError(req, res, 400, 'Too many txids requested');
return;
}
if (txids.some((txid) => !TXID_REGEX.test(txid))) {
handleError(req, res, 400, 'Invalid txids format');
return;
}
try {
const batchedOutspends = await bitcoinApi.$getBatchedOutspends(txids);
res.json(batchedOutspends);
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get batched outspends');
}
}
private async $getCpfpInfo(req: Request, res: Response) {
if (!/^[a-fA-F0-9]{64}$/.test(req.params.txId)) {
handleError(req, res, 501, `Invalid transaction ID.`);
if (!TXID_REGEX.test(req.params.txId)) {
handleError(req, res, 501, `Invalid transaction ID`);
return;
}
@@ -184,7 +196,7 @@ class BitcoinRoutes {
try {
cpfpInfo = await transactionRepository.$getCpfpInfo(req.params.txId);
} catch (e) {
handleError(req, res, 500, 'failed to get CPFP info');
handleError(req, res, 500, 'Failed to get CPFP info');
return;
}
}
@@ -205,6 +217,10 @@ class BitcoinRoutes {
}
private async getTransaction(req: Request, res: Response) {
if (!TXID_REGEX.test(req.params.txId)) {
handleError(req, res, 501, `Invalid transaction ID`);
return;
}
try {
const transaction = await transactionUtils.$getTransactionExtended(req.params.txId, true, false, false, true);
res.json(transaction);
@@ -212,12 +228,17 @@ class BitcoinRoutes {
let statusCode = 500;
if (e instanceof Error && e instanceof Error && e.message && e.message.indexOf('No such mempool or blockchain transaction') > -1) {
statusCode = 404;
handleError(req, res, statusCode, 'No such mempool or blockchain transaction');
}
handleError(req, res, statusCode, e instanceof Error ? e.message : e);
handleError(req, res, statusCode, 'Failed to get transaction');
}
}
private async getRawTransaction(req: Request, res: Response) {
if (!TXID_REGEX.test(req.params.txId)) {
handleError(req, res, 501, `Invalid transaction ID`);
return;
}
try {
const transaction: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(req.params.txId, true);
res.setHeader('content-type', 'text/plain');
@@ -226,8 +247,9 @@ class BitcoinRoutes {
let statusCode = 500;
if (e instanceof Error && e.message && e.message.indexOf('No such mempool or blockchain transaction') > -1) {
statusCode = 404;
handleError(req, res, statusCode, 'No such mempool or blockchain transaction');
}
handleError(req, res, statusCode, e instanceof Error ? e.message : e);
handleError(req, res, statusCode, 'Failed to get raw transaction');
}
}
@@ -292,14 +314,18 @@ class BitcoinRoutes {
}
} catch (e: any) {
if (e instanceof Error && new RegExp(notFoundError).test(e.message)) {
handleError(req, res, 404, e.message);
handleError(req, res, 404, notFoundError);
} else {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to process PSBT');
}
}
}
private async getTransactionStatus(req: Request, res: Response) {
if (!TXID_REGEX.test(req.params.txId)) {
handleError(req, res, 501, `Invalid transaction ID`);
return;
}
try {
const transaction = await transactionUtils.$getTransactionExtended(req.params.txId, true);
res.json(transaction.status);
@@ -307,36 +333,53 @@ class BitcoinRoutes {
let statusCode = 500;
if (e instanceof Error && e.message && e.message.indexOf('No such mempool or blockchain transaction') > -1) {
statusCode = 404;
handleError(req, res, statusCode, 'No such mempool or blockchain transaction');
}
handleError(req, res, statusCode, e instanceof Error ? e.message : e);
handleError(req, res, statusCode, 'Failed to get transaction status');
}
}
private async getStrippedBlockTransactions(req: Request, res: Response) {
if (!BLOCK_HASH_REGEX.test(req.params.hash)) {
handleError(req, res, 501, `Invalid block hash`);
return;
}
try {
const transactions = await blocks.$getStrippedBlockTransactions(req.params.hash);
res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24 * 30).toUTCString());
res.json(transactions);
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get block summary');
}
}
private async getStrippedBlockTransaction(req: Request, res: Response) {
if (!BLOCK_HASH_REGEX.test(req.params.hash)) {
handleError(req, res, 501, `Invalid block hash`);
return;
}
if (!TXID_REGEX.test(req.params.txid)) {
handleError(req, res, 501, `Invalid transaction ID`);
return;
}
try {
const transaction = await blocks.$getSingleTxFromSummary(req.params.hash, req.params.txid);
if (!transaction) {
handleError(req, res, 404, `transaction not found in summary`);
handleError(req, res, 404, `Transaction not found in summary`);
return;
}
res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24 * 30).toUTCString());
res.json(transaction);
} catch (e) {
res.status(500).send(e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get transaction from summary');
}
}
private async getBlock(req: Request, res: Response) {
if (!BLOCK_HASH_REGEX.test(req.params.hash)) {
handleError(req, res, 501, `Invalid block hash`);
return;
}
try {
const block = await blocks.$getBlock(req.params.hash);
@@ -348,53 +391,69 @@ class BitcoinRoutes {
} else if (blockAge > 30 * day) {
cacheDuration = 10 * day;
} else {
cacheDuration = 600
cacheDuration = 600;
}
res.setHeader('Expires', new Date(Date.now() + 1000 * cacheDuration).toUTCString());
res.json(block);
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get block');
}
}
private async getBlockHeader(req: Request, res: Response) {
if (!BLOCK_HASH_REGEX.test(req.params.hash)) {
handleError(req, res, 501, `Invalid block hash`);
return;
}
try {
const blockHeader = await bitcoinApi.$getBlockHeader(req.params.hash);
res.setHeader('content-type', 'text/plain');
res.send(blockHeader);
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get block header');
}
}
private async getBlockAuditSummary(req: Request, res: Response) {
if (!BLOCK_HASH_REGEX.test(req.params.hash)) {
handleError(req, res, 501, `Invalid block hash`);
return;
}
try {
const auditSummary = await blocks.$getBlockAuditSummary(req.params.hash);
if (auditSummary) {
res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24 * 30).toUTCString());
res.json(auditSummary);
} else {
handleError(req, res, 404, `audit not available`);
handleError(req, res, 404, `Audit not available`);
return;
}
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get block audit summary');
}
}
private async $getBlockTxAuditSummary(req: Request, res: Response) {
if (!BLOCK_HASH_REGEX.test(req.params.hash)) {
handleError(req, res, 501, `Invalid block hash`);
return;
}
if (!TXID_REGEX.test(req.params.txid)) {
handleError(req, res, 501, `Invalid transaction ID`);
return;
}
try {
const auditSummary = await blocks.$getBlockTxAuditSummary(req.params.hash, req.params.txid);
if (auditSummary) {
res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24 * 30).toUTCString());
res.json(auditSummary);
} else {
handleError(req, res, 404, `transaction audit not available`);
handleError(req, res, 404, `Transaction audit not available`);
return;
}
} catch (e) {
res.status(500).send(e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get transaction audit summary');
}
}
@@ -408,7 +467,7 @@ class BitcoinRoutes {
return await this.getLegacyBlocks(req, res);
}
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get blocks');
}
}
@@ -450,7 +509,7 @@ class BitcoinRoutes {
res.json(await blocks.$getBlocksBetweenHeight(from, to));
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get blocks');
}
}
@@ -485,11 +544,15 @@ class BitcoinRoutes {
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
res.json(returnBlocks);
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get blocks');
}
}
private async getBlockTransactions(req: Request, res: Response) {
if (!BLOCK_HASH_REGEX.test(req.params.hash)) {
handleError(req, res, 501, `Invalid block hash`);
return;
}
try {
loadingIndicators.setProgress('blocktxs-' + req.params.hash, 0);
@@ -510,7 +573,7 @@ class BitcoinRoutes {
res.json(transactions);
} catch (e) {
loadingIndicators.setProgress('blocktxs-' + req.params.hash, 100);
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get block transactions');
}
}
@@ -519,7 +582,7 @@ class BitcoinRoutes {
const blockHash = await bitcoinApi.$getBlockHash(parseInt(req.params.height, 10));
res.send(blockHash);
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get block at height');
}
}
@@ -528,16 +591,20 @@ class BitcoinRoutes {
handleError(req, res, 405, 'Address lookups cannot be used with bitcoind as backend.');
return;
}
if (!ADDRESS_REGEX.test(req.params.address)) {
handleError(req, res, 501, `Invalid address`);
return;
}
try {
const addressData = await bitcoinApi.$getAddress(req.params.address);
res.json(addressData);
} catch (e) {
if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
handleError(req, res, 413, e instanceof Error ? e.message : e);
handleError(req, res, 413, e.message);
return;
}
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get address');
}
}
@@ -546,6 +613,10 @@ class BitcoinRoutes {
handleError(req, res, 405, 'Address lookups cannot be used with bitcoind as backend.');
return;
}
if (!ADDRESS_REGEX.test(req.params.address)) {
handleError(req, res, 501, `Invalid address`);
return;
}
try {
let lastTxId: string = '';
@@ -556,10 +627,10 @@ class BitcoinRoutes {
res.json(transactions);
} catch (e) {
if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
handleError(req, res, 413, e instanceof Error ? e.message : e);
handleError(req, res, 413, e.message);
return;
}
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get address transactions');
}
}
@@ -575,6 +646,10 @@ class BitcoinRoutes {
handleError(req, res, 405, 'Address lookups cannot be used with bitcoind as backend.');
return;
}
if (!SCRIPT_HASH_REGEX.test(req.params.scripthash)) {
handleError(req, res, 501, `Invalid scripthash`);
return;
}
try {
// electrum expects scripthashes in little-endian
@@ -583,10 +658,10 @@ class BitcoinRoutes {
res.json(addressData);
} catch (e) {
if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
handleError(req, res, 413, e instanceof Error ? e.message : e);
handleError(req, res, 413, e.message);
return;
}
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get script hash');
}
}
@@ -595,6 +670,10 @@ class BitcoinRoutes {
handleError(req, res, 405, 'Address lookups cannot be used with bitcoind as backend.');
return;
}
if (!SCRIPT_HASH_REGEX.test(req.params.scripthash)) {
handleError(req, res, 501, `Invalid scripthash`);
return;
}
try {
// electrum expects scripthashes in little-endian
@@ -607,10 +686,10 @@ class BitcoinRoutes {
res.json(transactions);
} catch (e) {
if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
handleError(req, res, 413, e instanceof Error ? e.message : e);
handleError(req, res, 413, e.message);
return;
}
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get script hash transactions');
}
}
@@ -623,10 +702,10 @@ class BitcoinRoutes {
private async getAddressPrefix(req: Request, res: Response) {
try {
const blockHash = await bitcoinApi.$getAddressPrefix(req.params.prefix);
res.send(blockHash);
const addressPrefix = await bitcoinApi.$getAddressPrefix(req.params.prefix);
res.send(addressPrefix);
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get address prefix');
}
}
@@ -667,7 +746,7 @@ class BitcoinRoutes {
res.setHeader('content-type', 'text/plain');
res.send(result.toString());
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get height at tip');
}
}
@@ -677,39 +756,55 @@ class BitcoinRoutes {
res.setHeader('content-type', 'text/plain');
res.send(result);
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get hash at tip');
}
}
private async getRawBlock(req: Request, res: Response) {
if (!BLOCK_HASH_REGEX.test(req.params.hash)) {
handleError(req, res, 501, `Invalid block hash`);
return;
}
try {
const result = await bitcoinApi.$getRawBlock(req.params.hash);
res.setHeader('content-type', 'application/octet-stream');
res.send(result);
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get raw block');
}
}
private async getTxIdsForBlock(req: Request, res: Response) {
if (!BLOCK_HASH_REGEX.test(req.params.hash)) {
handleError(req, res, 501, `Invalid block hash`);
return;
}
try {
const result = await bitcoinApi.$getTxIdsForBlock(req.params.hash);
res.json(result);
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get txids for block');
}
}
private async validateAddress(req: Request, res: Response) {
if (!ADDRESS_REGEX.test(req.params.address)) {
handleError(req, res, 501, `Invalid address`);
return;
}
try {
const result = await bitcoinClient.validateAddress(req.params.address);
res.json(result);
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to validate address');
}
}
private async getRbfHistory(req: Request, res: Response) {
if (!TXID_REGEX.test(req.params.txId)) {
handleError(req, res, 501, `Invalid transaction ID`);
return;
}
try {
const replacements = rbfCache.getRbfTree(req.params.txId) || null;
const replaces = rbfCache.getReplaces(req.params.txId) || null;
@@ -718,7 +813,7 @@ class BitcoinRoutes {
replaces
});
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get rbf history');
}
}
@@ -727,7 +822,7 @@ class BitcoinRoutes {
const result = rbfCache.getRbfTrees(false);
res.json(result);
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get rbf trees');
}
}
@@ -736,11 +831,15 @@ class BitcoinRoutes {
const result = rbfCache.getRbfTrees(true);
res.json(result);
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get full rbf replacements');
}
}
private async getCachedTx(req: Request, res: Response) {
if (!TXID_REGEX.test(req.params.txId)) {
handleError(req, res, 501, `Invalid transaction ID`);
return;
}
try {
const result = rbfCache.getTx(req.params.txId);
if (result) {
@@ -749,16 +848,20 @@ class BitcoinRoutes {
res.status(204).send();
}
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get cached tx');
}
}
private async getTransactionOutspends(req: Request, res: Response) {
if (!TXID_REGEX.test(req.params.txId)) {
handleError(req, res, 501, `Invalid transaction ID`);
return;
}
try {
const result = await bitcoinApi.$getOutspends(req.params.txId);
res.json(result);
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get transaction outspends');
}
}
@@ -771,7 +874,7 @@ class BitcoinRoutes {
handleError(req, res, 503, `Service Temporarily Unavailable`);
}
} catch (e) {
handleError(req, res, 500, e instanceof Error ? e.message : e);
handleError(req, res, 500, 'Failed to get difficulty change');
}
}
@@ -782,8 +885,8 @@ class BitcoinRoutes {
const txIdResult = await bitcoinApi.$sendRawTransaction(rawTx);
res.send(txIdResult);
} catch (e: any) {
handleError(req, res, 400, e.message && e.code ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
: (e.message || 'Error'));
handleError(req, res, 400, (e.message && e.code) ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code })
: 'Failed to send raw transaction');
}
}
@@ -794,8 +897,8 @@ class BitcoinRoutes {
const txIdResult = await bitcoinClient.sendRawTransaction(txHex);
res.send(txIdResult);
} catch (e: any) {
handleError(req, res, 400, e.message && e.code ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
: (e.message || 'Error'));
handleError(req, res, 400, (e.message && e.code) ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code })
: 'Failed to send raw transaction');
}
}
@@ -806,8 +909,8 @@ class BitcoinRoutes {
const result = await bitcoinApi.$testMempoolAccept(rawTxs, maxfeerate);
res.send(result);
} catch (e: any) {
handleError(req, res, 400, e.message && e.code ? 'testmempoolaccept RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
: (e.message || 'Error'));
handleError(req, res, 400, (e.message && e.code) ? 'testmempoolaccept RPC error: ' + JSON.stringify({ code: e.code })
: 'Failed to test transactions');
}
}
@@ -819,8 +922,8 @@ class BitcoinRoutes {
const result = await bitcoinClient.submitPackage(rawTxs, maxfeerate ?? undefined, maxburnamount ?? undefined);
res.send(result);
} catch (e: any) {
handleError(req, res, 400, e.message && e.code ? 'submitpackage RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
: (e.message || 'Error'));
handleError(req, res, 400, (e.message && e.code) ? 'submitpackage RPC error: ' + JSON.stringify({ code: e.code })
: 'Failed to submit package');
}
}