Skip to content

Commit 335b567

Browse files
Oleksandr Fedorenkophilippem
authored andcommitted
fix error handling for GET /block/:hash/txs/:index
1 parent 4e1fe8e commit 335b567

2 files changed

Lines changed: 73 additions & 5 deletions

File tree

src/rest.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -780,13 +780,19 @@ fn handle_request(
780780
}
781781
(&Method::GET, Some(&"block"), Some(hash), Some(&"txs"), start_index, None) => {
782782
let hash = BlockHash::from_str(hash)?;
783+
784+
// Add lightweight validation that block exists before fetching transactions,
785+
// to avoid expensive lookups in case of invalid block hash
786+
query.chain().get_block_header(&hash)
787+
.ok_or_else(|| HttpError::not_found("Block not found".to_string()))?;
788+
783789
let start_index = start_index
784790
.map_or(0u32, |el| el.parse().unwrap_or(0))
785791
.max(0u32) as usize;
786792

787793
ensure!(
788794
start_index % CHAIN_TXS_PER_PAGE == 0,
789-
"start index must be a multipication of {}",
795+
"start index must be a multiple of {}",
790796
CHAIN_TXS_PER_PAGE
791797
);
792798

tests/rest.rs

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,22 @@ fn test_rest_block() -> Result<()> {
328328
tester.node_client().call::<String>("getblock", &[blockhash.to_string().into(), 0.into()])?;
329329
assert_eq!(rest_rawblock, Vec::from_hex(&node_hexblock).unwrap());
330330

331+
// Test GET /block/:hash/txid/:index
332+
let res = get_plain(rest_addr, &format!("/block/{}/txid/1", blockhash))?;
333+
assert_eq!(res, txid.to_string());
334+
335+
rest_handle.stop();
336+
Ok(())
337+
}
338+
339+
#[test]
340+
fn test_rest_block_txs() -> Result<()> {
341+
let (rest_handle, rest_addr, mut tester) = common::init_rest_tester().unwrap();
342+
343+
let addr1 = tester.newaddress()?;
344+
let txid = tester.send(&addr1, "0.98765432 BTC".parse().unwrap())?;
345+
let blockhash = tester.mine()?;
346+
331347
// Test GET /block/:hash/txs
332348
let res = get_json(rest_addr, &format!("/block/{}/txs", blockhash))?;
333349
let block_txs = res.as_array().expect("list of txs");
@@ -338,9 +354,55 @@ fn test_rest_block() -> Result<()> {
338354
Some(txid.to_string().as_str())
339355
);
340356

341-
// Test GET /block/:hash/txid/:index
342-
let res = get_plain(rest_addr, &format!("/block/{}/txid/1", blockhash))?;
343-
assert_eq!(res, txid.to_string());
357+
// Test GET /block/:hash/txs/:index
358+
let res = get_json(rest_addr, &format!("/block/{}/txs/0", blockhash))?;
359+
let block_txs = res.as_array().expect("list of txs");
360+
assert_eq!(block_txs.len(), 2);
361+
assert_eq!(block_txs[0]["vin"][0]["is_coinbase"].as_bool(), Some(true));
362+
assert_eq!(
363+
block_txs[1]["txid"].as_str(),
364+
Some(txid.to_string().as_str())
365+
);
366+
367+
// Test GET /block/:hash/txs/:index
368+
// Should fail with 404 code when block isn't found
369+
let invalid_resp = ureq::get(&format!("http://{}/block/{}/txs/0", rest_addr, "0000000000000000000000000000000000000000000000000000000000000000"))
370+
.config()
371+
.http_status_as_error(false)
372+
.build()
373+
.call()?;
374+
assert_eq!(invalid_resp.status(), 404);
375+
assert_eq!(invalid_resp.into_body().read_to_string()?, "Block not found");
376+
377+
// Test GET /block/:hash/txs/:index
378+
// Should fail with 400 code when block hash is invalid
379+
let invalid_resp = ureq::get(&format!("http://{}/block/{}/txs/0", rest_addr, "invalid_hash"))
380+
.config()
381+
.http_status_as_error(false)
382+
.build()
383+
.call()?;
384+
assert_eq!(invalid_resp.status(), 400);
385+
assert_eq!(invalid_resp.into_body().read_to_string()?, "Invalid hex string");
386+
387+
// Test GET /block/:hash/txs/:index
388+
// Should fail with 400 code when `(index % 25) != 0`
389+
let invalid_hash_resp = ureq::get(&format!("http://{}/block/{}/txs/1", rest_addr, blockhash))
390+
.config()
391+
.http_status_as_error(false)
392+
.build()
393+
.call()?;
394+
assert_eq!(invalid_hash_resp.status(), 400);
395+
assert_eq!(invalid_hash_resp.into_body().read_to_string()?, "start index must be a multiple of 25");
396+
397+
// Test GET /block/:hash/txs/:index
398+
// Should fail with 400 code when index is out of range
399+
let invalid_hash_resp = ureq::get(&format!("http://{}/block/{}/txs/25", rest_addr, blockhash))
400+
.config()
401+
.http_status_as_error(false)
402+
.build()
403+
.call()?;
404+
assert_eq!(invalid_hash_resp.status(), 400);
405+
assert_eq!(invalid_hash_resp.into_body().read_to_string()?, "start index out of range");
344406

345407
rest_handle.stop();
346408
Ok(())
@@ -1043,7 +1105,7 @@ fn test_rest_reorg() -> Result<()> {
10431105
#[cfg(not(feature = "liquid"))]
10441106
#[test]
10451107
fn test_rest_submit_package() -> Result<()> {
1046-
let (rest_handle, rest_addr, mut tester) = common::init_rest_tester().unwrap();
1108+
let (rest_handle, rest_addr, tester) = common::init_rest_tester().unwrap();
10471109

10481110
// Test with a real transaction package - create parent-child transactions
10491111
// submitpackage requires between 2 and 25 transactions with proper dependencies

0 commit comments

Comments
 (0)