diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index ab5b0a73a..ec63f4ff8 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -751,13 +751,13 @@ class BitcoinRoutes { } private async $testTransactions(req: Request, res: Response) { - res.setHeader('content-type', 'text/plain'); try { const rawTxs = Common.getTransactionsFromRequest(req); const maxfeerate = parseFloat(req.query.maxfeerate as string); const result = await bitcoinApi.$testMempoolAccept(rawTxs, maxfeerate); res.send(result); } catch (e: any) { + res.setHeader('content-type', 'text/plain'); res.status(400).send(e.message && e.code ? 'testmempoolaccept RPC error: ' + JSON.stringify({ code: e.code, message: e.message }) : (e.message || 'Error')); } diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index ddbe98ec5..2c9338814 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -946,12 +946,16 @@ export class Common { return this.validateTransactionHex(matches[1].toLowerCase()); } - static getTransactionsFromRequest(req: Request): string[] { - if (typeof req.body !== 'string') { - throw Object.assign(new Error('Non-string request body'), { code: -1 }); + static getTransactionsFromRequest(req: Request, limit: number = 25): string[] { + if (!Array.isArray(req.body) || req.body.some(hex => typeof hex !== 'string')) { + throw Object.assign(new Error('Invalid request body (should be an array of hexadecimal strings)'), { code: -1 }); } - const txs = req.body.split(','); + if (limit && req.body.length > limit) { + throw Object.assign(new Error('Exceeded maximum of 25 transactions'), { code: -1 }); + } + + const txs = req.body; return txs.map(rawTx => { // Support both upper and lower case hex diff --git a/backend/src/index.ts b/backend/src/index.ts index b088155ea..df9f7dc65 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -131,6 +131,7 @@ class Server { }) .use(express.urlencoded({ extended: true })) .use(express.text({ type: ['text/plain', 'application/base64'] })) + .use(express.json()) ; if (config.DATABASE.ENABLED && config.FIAT_PRICE.ENABLED) { diff --git a/frontend/src/app/components/test-transactions/test-transactions.component.scss b/frontend/src/app/components/test-transactions/test-transactions.component.scss index 399575b8e..ffdd5811b 100644 --- a/frontend/src/app/components/test-transactions/test-transactions.component.scss +++ b/frontend/src/app/components/test-transactions/test-transactions.component.scss @@ -2,6 +2,7 @@ td, th { &.allowed { width: 10%; + text-align: center; } &.txid { width: 50%; diff --git a/frontend/src/app/components/test-transactions/test-transactions.component.ts b/frontend/src/app/components/test-transactions/test-transactions.component.ts index c959cb2b0..7291dae20 100644 --- a/frontend/src/app/components/test-transactions/test-transactions.component.ts +++ b/frontend/src/app/components/test-transactions/test-transactions.component.ts @@ -37,6 +37,21 @@ export class TestTransactionsComponent implements OnInit { } testTxs() { + let txs: string[] = []; + try { + txs = (this.testTxsForm.get('txs')?.value as string).split(',').map(hex => hex.trim()); + if (!txs?.length) { + this.error = 'At least one transaction is required'; + return; + } else if (txs.length > 25) { + this.error = 'Exceeded maximum of 25 transactions'; + return; + } + } catch (e) { + this.error = e?.message; + return; + } + let maxfeerate; this.invalidMaxfeerate = false; try { @@ -51,7 +66,7 @@ export class TestTransactionsComponent implements OnInit { this.isLoading = true; this.error = ''; this.results = []; - this.apiService.testTransactions$((this.testTxsForm.get('txs')?.value as string).split(',').map(hex => hex.trim()).join(','), maxfeerate === 0.1 ? null : maxfeerate) + this.apiService.testTransactions$(txs, maxfeerate === 0.1 ? null : maxfeerate) .subscribe((result) => { this.isLoading = false; this.results = result || []; diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index e41f8eb9e..0531916a0 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -238,8 +238,8 @@ export class ApiService { return this.httpClient.post(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'}); } - testTransactions$(hexPayload: string, maxfeerate?: number): Observable { - return this.httpClient.post(this.apiBaseUrl + this.apiBasePath + `/api/txs/test${maxfeerate != null ? '?maxfeerate=' + maxfeerate.toFixed(8) : ''}`, hexPayload); + testTransactions$(rawTxs: string[], maxfeerate?: number): Observable { + return this.httpClient.post(this.apiBaseUrl + this.apiBasePath + `/api/txs/test${maxfeerate != null ? '?maxfeerate=' + maxfeerate.toFixed(8) : ''}`, rawTxs); } getTransactionStatus$(txid: string): Observable {