Files
lighthouse/beacon_node/http_api/tests/status_tests.rs
dapplion e6b5b441a5 Fix Gloas http-api-tests failures
Two tests fail under FORK_NAME=gloas; only the first surfaces in CI
because nextest aborts on the first failure.

1. status_tests::node_health_el_online_and_not_synced

   The test simulates "EL online but not synced" via
   mock_el.server.all_payloads_syncing(true), expecting the head to
   become optimistic so the endpoint returns 206. In Gloas, blocks
   don't carry execution payloads — the payload arrives via an
   envelope, so newPayload is never called during block import and
   the head is never marked optimistic. The endpoint correctly
   returns 200. Skip the test for Gloas, matching the existing
   pattern on el_error_on_new_payload.

2. tests::get_validator_payload_attestation_data

   Two issues stacked:
   - The test used ApiTester::new() (default phase0 spec) so the
     chain wasn't actually at the Gloas fork even with
     FORK_NAME=gloas. Switch to new_with_hard_forks(), which uses
     test_spec() and respects FORK_NAME.
   - produce_payload_attestation_data requires
     head.slot == request_slot, but the harness leaves the slot
     clock at head_slot + 1 with no block produced for that slot.
     Rewind the slot clock to the head slot in the test helper.

Full Gloas http-api suite: 193 tests run: 193 passed.
2026-04-27 09:17:25 +02:00

239 lines
8.1 KiB
Rust

//! Tests related to the beacon node's sync status
use beacon_chain::{
BlockError,
test_utils::{
AttestationStrategy, BlockStrategy, LightClientStrategy, SyncCommitteeStrategy,
fork_name_from_env, test_spec,
},
};
use execution_layer::{PayloadStatusV1, PayloadStatusV1Status};
use http_api::test_utils::InteractiveTester;
use reqwest::StatusCode;
use types::{EthSpec, ExecPayload, MinimalEthSpec, Slot, Uint256};
type E = MinimalEthSpec;
/// Create a new test environment that is post-merge with `chain_depth` blocks.
async fn post_merge_tester(chain_depth: u64, validator_count: u64) -> InteractiveTester<E> {
let mut spec = test_spec::<E>();
spec.terminal_total_difficulty = Uint256::from(1);
let tester = InteractiveTester::<E>::new(Some(spec), validator_count as usize).await;
let harness = &tester.harness;
let mock_el = harness.mock_execution_layer.as_ref().unwrap();
mock_el.server.all_payloads_valid();
// Create some chain depth.
harness.advance_slot();
harness
.extend_chain_with_sync(
chain_depth as usize,
BlockStrategy::OnCanonicalHead,
AttestationStrategy::AllValidators,
SyncCommitteeStrategy::AllValidators,
LightClientStrategy::Disabled,
)
.await;
tester
}
/// Check `syncing` endpoint when the EL is syncing.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn el_syncing_then_synced() {
let num_blocks = E::slots_per_epoch() / 2;
let num_validators = E::slots_per_epoch();
let tester = post_merge_tester(num_blocks, num_validators).await;
let harness = &tester.harness;
let mock_el = harness.mock_execution_layer.as_ref().unwrap();
// EL syncing
mock_el.server.set_syncing_response(Ok(true));
mock_el.el.upcheck().await;
let api_response = tester.client.get_node_syncing().await.unwrap().data;
assert!(!api_response.el_offline);
assert!(!api_response.is_optimistic);
assert!(!api_response.is_syncing);
// EL synced
mock_el.server.set_syncing_response(Ok(false));
mock_el.el.upcheck().await;
let api_response = tester.client.get_node_syncing().await.unwrap().data;
assert!(!api_response.el_offline);
assert!(!api_response.is_optimistic);
assert!(!api_response.is_syncing);
}
/// Check `syncing` endpoint when the EL is offline (errors on upcheck).
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn el_offline() {
let num_blocks = E::slots_per_epoch() / 2;
let num_validators = E::slots_per_epoch();
let tester = post_merge_tester(num_blocks, num_validators).await;
let harness = &tester.harness;
let mock_el = harness.mock_execution_layer.as_ref().unwrap();
// EL offline
mock_el.server.set_syncing_response(Err("offline".into()));
mock_el.el.upcheck().await;
let api_response = tester.client.get_node_syncing().await.unwrap().data;
assert!(api_response.el_offline);
assert!(!api_response.is_optimistic);
assert!(!api_response.is_syncing);
}
/// Check `syncing` endpoint when the EL errors on newPaylod but is not fully offline.
// Gloas blocks don't carry execution payloads — the payload arrives via an envelope,
// so newPayload is never called during block import. Skip for Gloas.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn el_error_on_new_payload() {
if fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
return;
}
let num_blocks = E::slots_per_epoch() / 2;
let num_validators = E::slots_per_epoch();
let tester = post_merge_tester(num_blocks, num_validators).await;
let harness = &tester.harness;
let mock_el = harness.mock_execution_layer.as_ref().unwrap();
// Make a block.
let pre_state = harness.get_current_state();
let (block_contents, _) = harness
.make_block(pre_state, Slot::new(num_blocks + 1))
.await;
let (block, blobs) = block_contents;
let block_hash = block
.message()
.body()
.execution_payload()
.unwrap()
.block_hash();
// Make sure `newPayload` errors for the new block.
mock_el
.server
.set_new_payload_error(block_hash, "error".into());
// Attempt to process the block, which should error.
harness.advance_slot();
assert!(matches!(
harness
.process_block_result((block.clone(), blobs.clone()))
.await,
Err(BlockError::ExecutionPayloadError(_))
));
// The EL should now be *offline* according to the API.
let api_response = tester.client.get_node_syncing().await.unwrap().data;
assert!(api_response.el_offline);
assert!(!api_response.is_optimistic);
assert!(!api_response.is_syncing);
// Processing a block successfully should remove the status.
mock_el.server.set_new_payload_status(
block_hash,
PayloadStatusV1 {
status: PayloadStatusV1Status::Valid,
latest_valid_hash: Some(block_hash),
validation_error: None,
},
);
harness.process_block_result((block, blobs)).await.unwrap();
let api_response = tester.client.get_node_syncing().await.unwrap().data;
assert!(!api_response.el_offline);
assert!(!api_response.is_optimistic);
assert!(!api_response.is_syncing);
}
/// Check `node health` endpoint when the EL is offline.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn node_health_el_offline() {
let num_blocks = E::slots_per_epoch() / 2;
let num_validators = E::slots_per_epoch();
let tester = post_merge_tester(num_blocks, num_validators).await;
let harness = &tester.harness;
let mock_el = harness.mock_execution_layer.as_ref().unwrap();
// EL offline
mock_el.server.set_syncing_response(Err("offline".into()));
mock_el.el.upcheck().await;
let status = tester.client.get_node_health().await;
match status {
Ok(_) => {
panic!("should return 503 error status code");
}
Err(e) => {
assert_eq!(e.status().unwrap(), 503);
}
}
}
/// Check `node health` endpoint when the EL is online and synced.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn node_health_el_online_and_synced() {
let num_blocks = E::slots_per_epoch() / 2;
let num_validators = E::slots_per_epoch();
let tester = post_merge_tester(num_blocks, num_validators).await;
let harness = &tester.harness;
let mock_el = harness.mock_execution_layer.as_ref().unwrap();
// EL synced
mock_el.server.set_syncing_response(Ok(false));
mock_el.el.upcheck().await;
let status = tester.client.get_node_health().await;
match status {
Ok(response) => {
assert_eq!(response, StatusCode::OK);
}
Err(_) => {
panic!("should return 200 status code");
}
}
}
/// Check `node health` endpoint when the EL is online but not synced.
// Gloas blocks don't carry execution payloads — the payload arrives via an envelope,
// so newPayload is never called during block import and the head is not marked
// optimistic when `all_payloads_syncing(true)`. Skip for Gloas.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn node_health_el_online_and_not_synced() {
if fork_name_from_env().is_some_and(|f| f.gloas_enabled()) {
return;
}
let num_blocks = E::slots_per_epoch() / 2;
let num_validators = E::slots_per_epoch();
let tester = post_merge_tester(num_blocks, num_validators).await;
let harness = &tester.harness;
let mock_el = harness.mock_execution_layer.as_ref().unwrap();
// EL not synced
harness.advance_slot();
mock_el.server.all_payloads_syncing(true);
harness
.extend_chain(
1,
BlockStrategy::OnCanonicalHead,
AttestationStrategy::AllValidators,
)
.await;
let status = tester.client.get_node_health().await;
match status {
Ok(response) => {
assert_eq!(response, StatusCode::PARTIAL_CONTENT);
}
Err(_) => {
panic!("should return 206 status code");
}
}
}