Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion crates/networking/rpc/eth/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,5 +453,27 @@ pub async fn get_all_block_receipts(
return Ok(Vec::new());
}
let block_hash = header.hash();
Ok(storage.get_receipts_for_block(&block_hash).await?)
let receipts = storage.get_receipts_for_block(&block_hash).await?;
// `get_receipts_for_block` returns a bare Vec, so a block whose receipts are
// absent is indistinguishable from a block that genuinely has none. Returning
// the empty list would be a wrong answer rather than a reported failure, so
// check it against the block's own transaction count. This mirrors the
// mismatch check the by-index receipt path already performs.
let expected = match storage.get_block_body_by_hash(block_hash).await? {
Some(body) => body.transactions.len(),
// No body means the block's history is not retained; without it there is
// nothing to validate the receipt count against.
None => {
return Err(RpcErr::Internal(format!(
"Body unavailable for block {block_hash:#x}, cannot serve its receipts"
)));
}
};
if receipts.len() != expected {
return Err(RpcErr::Internal(format!(
"Expected {expected} receipts for block {block_hash:#x}, got {}",
receipts.len()
)));
}
Ok(receipts)
}
1 change: 1 addition & 0 deletions test/tests/rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod estimate_gas_tests;
mod fork_choice_tests;
mod http_batch_tests;
mod missing_rpc_methods_tests;
mod raw_receipts_completeness_tests;
mod send_raw_transaction_tests;
mod subscription_manager_tests;
mod trace_call_tests;
63 changes: 63 additions & 0 deletions test/tests/rpc/raw_receipts_completeness_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//! `debug_getRawReceipts` must not present a block's receipts as empty when they
//! are merely absent.
//!
//! `get_receipts_for_block` returns a bare `Vec`, so "this block has no receipts
//! stored" and "this block had no transactions" are the same value. Handing back
//! the empty list is a wrong answer rather than a reported failure, and once
//! history pruning lands (#6673) an absent receipt set becomes a normal
//! steady-state outcome rather than a corruption signal.

use ethrex_rpc::test_utils::{
add_legacy_tx_blocks, call_http, default_context_with_storage, setup_store,
};

#[tokio::test]
async fn raw_receipts_reports_a_block_whose_receipts_are_absent() {
let store = setup_store().await;
// The harness stores blocks and their transactions but no receipts, which is
// exactly the shape a pruned block presents: body present, receipts gone.
add_legacy_tx_blocks(&store, 1, 1).await;
let context = default_context_with_storage(store).await;

let response = call_http(
context,
r#"{"jsonrpc":"2.0","method":"debug_getRawReceipts","params":["0x1"],"id":1}"#.to_string(),
)
.await;

assert!(
response.get("error").is_some(),
"a block with 1 transaction and no stored receipts must report a failure \
rather than returning an empty list; got {response}"
);
assert_ne!(
response["result"].as_array().map(|a| a.len()),
Some(0),
"must not answer with an empty receipt list: {response}"
);
}

#[tokio::test]
async fn raw_receipts_still_serves_genesis_as_empty() {
let store = setup_store().await;
add_legacy_tx_blocks(&store, 1, 1).await;
let context = default_context_with_storage(store).await;

// Genesis legitimately has no receipts and is short-circuited before the
// completeness check, so it must keep answering with an empty list.
let response = call_http(
context,
r#"{"jsonrpc":"2.0","method":"debug_getRawReceipts","params":["0x0"],"id":1}"#.to_string(),
)
.await;

assert!(
response.get("error").is_none(),
"genesis must not be reported as a failure: {response}"
);
assert_eq!(
response["result"].as_array().map(|a| a.len()),
Some(0),
"genesis has no receipts: {response}"
);
}
Loading