testmempool accept more validation & switch to JSON array format

This commit is contained in:
Mononaut 2024-03-25 05:52:03 +00:00 committed by softsimon
parent f3232b2d5c
commit 2a43255802
No known key found for this signature in database
GPG Key ID: 488D7DCFB5A430D7
6 changed files with 29 additions and 8 deletions

View File

@ -751,13 +751,13 @@ class BitcoinRoutes {
} }
private async $testTransactions(req: Request, res: Response) { private async $testTransactions(req: Request, res: Response) {
res.setHeader('content-type', 'text/plain');
try { try {
const rawTxs = Common.getTransactionsFromRequest(req); const rawTxs = Common.getTransactionsFromRequest(req);
const maxfeerate = parseFloat(req.query.maxfeerate as string); const maxfeerate = parseFloat(req.query.maxfeerate as string);
const result = await bitcoinApi.$testMempoolAccept(rawTxs, maxfeerate); const result = await bitcoinApi.$testMempoolAccept(rawTxs, maxfeerate);
res.send(result); res.send(result);
} catch (e: any) { } 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 }) res.status(400).send(e.message && e.code ? 'testmempoolaccept RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
: (e.message || 'Error')); : (e.message || 'Error'));
} }

View File

@ -946,12 +946,16 @@ export class Common {
return this.validateTransactionHex(matches[1].toLowerCase()); return this.validateTransactionHex(matches[1].toLowerCase());
} }
static getTransactionsFromRequest(req: Request): string[] { static getTransactionsFromRequest(req: Request, limit: number = 25): string[] {
if (typeof req.body !== 'string') { if (!Array.isArray(req.body) || req.body.some(hex => typeof hex !== 'string')) {
throw Object.assign(new Error('Non-string request body'), { code: -1 }); 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 => { return txs.map(rawTx => {
// Support both upper and lower case hex // Support both upper and lower case hex

View File

@ -131,6 +131,7 @@ class Server {
}) })
.use(express.urlencoded({ extended: true })) .use(express.urlencoded({ extended: true }))
.use(express.text({ type: ['text/plain', 'application/base64'] })) .use(express.text({ type: ['text/plain', 'application/base64'] }))
.use(express.json())
; ;
if (config.DATABASE.ENABLED && config.FIAT_PRICE.ENABLED) { if (config.DATABASE.ENABLED && config.FIAT_PRICE.ENABLED) {

View File

@ -2,6 +2,7 @@
td, th { td, th {
&.allowed { &.allowed {
width: 10%; width: 10%;
text-align: center;
} }
&.txid { &.txid {
width: 50%; width: 50%;

View File

@ -37,6 +37,21 @@ export class TestTransactionsComponent implements OnInit {
} }
testTxs() { 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; let maxfeerate;
this.invalidMaxfeerate = false; this.invalidMaxfeerate = false;
try { try {
@ -51,7 +66,7 @@ export class TestTransactionsComponent implements OnInit {
this.isLoading = true; this.isLoading = true;
this.error = ''; this.error = '';
this.results = []; 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) => { .subscribe((result) => {
this.isLoading = false; this.isLoading = false;
this.results = result || []; this.results = result || [];

View File

@ -238,8 +238,8 @@ export class ApiService {
return this.httpClient.post<any>(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'}); return this.httpClient.post<any>(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'});
} }
testTransactions$(hexPayload: string, maxfeerate?: number): Observable<TestMempoolAcceptResult[]> { testTransactions$(rawTxs: string[], maxfeerate?: number): Observable<TestMempoolAcceptResult[]> {
return this.httpClient.post<TestMempoolAcceptResult[]>(this.apiBaseUrl + this.apiBasePath + `/api/txs/test${maxfeerate != null ? '?maxfeerate=' + maxfeerate.toFixed(8) : ''}`, hexPayload); return this.httpClient.post<TestMempoolAcceptResult[]>(this.apiBaseUrl + this.apiBasePath + `/api/txs/test${maxfeerate != null ? '?maxfeerate=' + maxfeerate.toFixed(8) : ''}`, rawTxs);
} }
getTransactionStatus$(txid: string): Observable<any> { getTransactionStatus$(txid: string): Observable<any> {