From 9065e4a56e7327d7c084e996d5ee071a0eec038b Mon Sep 17 00:00:00 2001 From: 0xMushow <105550256+0xMushow@users.noreply.github.com> Date: Thu, 4 Dec 2025 14:35:12 +0100 Subject: [PATCH 1/6] fix(beacon_node): add pruning of observed_column_sidecars (#8531) None I noticed that `observed_column_sidecars` is missing its prune call in the finalization handler, which results in a memory leak on long-running nodes (very slow (**7MB/day**)) : https://github.com/sigp/lighthouse/blob/13dfa9200f822c41ccd81b95a3f052df54c888e9/beacon_node/beacon_chain/src/canonical_head.rs#L940-L959 Both caches use the same generic type `ObservedDataSidecars:` https://github.com/sigp/lighthouse/blob/22ec4b327186c4a4a87d2c8c745caf3b36cb6dd6/beacon_node/beacon_chain/src/beacon_chain.rs#L413-L416 The type's documentation explicitly requires manual pruning: > "*The cache supports pruning based upon the finalized epoch. It does not automatically prune, you must call Self::prune manually.*" https://github.com/sigp/lighthouse/blob/b4704eab4ac8edf0ea0282ed9a5758b784038dd2/beacon_node/beacon_chain/src/observed_data_sidecars.rs#L66-L74 Currently: - `observed_blob_sidecars` => pruned - `observed_column_sidecars` => **NOT** pruned Without pruning, the underlying HashMap accumulates entries indefinitely, causing continuous memory growth until the node restarts. Co-Authored-By: Antoine James --- beacon_node/beacon_chain/src/canonical_head.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/beacon_node/beacon_chain/src/canonical_head.rs b/beacon_node/beacon_chain/src/canonical_head.rs index db071db166..1a08ac3f88 100644 --- a/beacon_node/beacon_chain/src/canonical_head.rs +++ b/beacon_node/beacon_chain/src/canonical_head.rs @@ -918,6 +918,13 @@ impl BeaconChain { .start_slot(T::EthSpec::slots_per_epoch()), ); + self.observed_column_sidecars.write().prune( + new_view + .finalized_checkpoint + .epoch + .start_slot(T::EthSpec::slots_per_epoch()), + ); + self.observed_slashable.write().prune( new_view .finalized_checkpoint From d4ec006a3419f15041a02792b1981e68645c501d Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Wed, 18 Feb 2026 14:01:22 +1100 Subject: [PATCH 2/6] Update `time` to fix `cargo audit` failure (#8764) --- Cargo.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8748be726c..7d75f5c197 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6323,9 +6323,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-integer" @@ -8899,30 +8899,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", From c61665b3a1efc6b353b57be37816e69825f2bab6 Mon Sep 17 00:00:00 2001 From: Akihito Nakano Date: Wed, 11 Feb 2026 07:49:20 +0900 Subject: [PATCH 3/6] Penalize peers that send an invalid rpc request (#6986) Since https://github.com/sigp/lighthouse/pull/6847, invalid `BlocksByRange`/`BlobsByRange` requests, which do not comply with the spec, are [handled in the Handler](https://github.com/sigp/lighthouse/blob/3d16d1080f5b93193404967dcb5525fa68840ea0/beacon_node/lighthouse_network/src/rpc/handler.rs#L880-L911). Any peer that sends an invalid request is penalized and disconnected. However, other kinds of invalid rpc request, which result in decoding errors, are just dropped. No penalty is applied and the connection with the peer remains. I have added handling for the `ListenUpgradeError` event to notify the application of an `RPCError:InvalidData` error and disconnect to the peer that sent the invalid rpc request. I also added tests for handling invalid rpc requests. Co-Authored-By: ackintosh --- .../lighthouse_network/src/rpc/handler.rs | 17 +- .../lighthouse_network/src/rpc/protocol.rs | 10 +- .../lighthouse_network/tests/rpc_tests.rs | 160 +++++++++++++++++- 3 files changed, 179 insertions(+), 8 deletions(-) diff --git a/beacon_node/lighthouse_network/src/rpc/handler.rs b/beacon_node/lighthouse_network/src/rpc/handler.rs index 720895bbe7..9861119ac1 100644 --- a/beacon_node/lighthouse_network/src/rpc/handler.rs +++ b/beacon_node/lighthouse_network/src/rpc/handler.rs @@ -13,7 +13,8 @@ use futures::prelude::*; use libp2p::PeerId; use libp2p::swarm::handler::{ ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, DialUpgradeError, - FullyNegotiatedInbound, FullyNegotiatedOutbound, StreamUpgradeError, SubstreamProtocol, + FullyNegotiatedInbound, FullyNegotiatedOutbound, ListenUpgradeError, StreamUpgradeError, + SubstreamProtocol, }; use libp2p::swarm::{ConnectionId, Stream}; use logging::crit; @@ -888,6 +889,16 @@ where ConnectionEvent::DialUpgradeError(DialUpgradeError { info, error }) => { self.on_dial_upgrade_error(info, error) } + ConnectionEvent::ListenUpgradeError(ListenUpgradeError { + error: (proto, error), + .. + }) => { + self.events_out.push(HandlerEvent::Err(HandlerErr::Inbound { + id: self.current_inbound_substream_id, + proto, + error, + })); + } _ => { // NOTE: ConnectionEvent is a non exhaustive enum so updates should be based on // release notes more than compiler feedback @@ -924,7 +935,7 @@ where request.count() )), })); - return self.shutdown(None); + return; } } RequestType::BlobsByRange(request) => { @@ -940,7 +951,7 @@ where max_allowed, max_requested_blobs )), })); - return self.shutdown(None); + return; } } _ => {} diff --git a/beacon_node/lighthouse_network/src/rpc/protocol.rs b/beacon_node/lighthouse_network/src/rpc/protocol.rs index f0ac9d00f9..34d8efccd1 100644 --- a/beacon_node/lighthouse_network/src/rpc/protocol.rs +++ b/beacon_node/lighthouse_network/src/rpc/protocol.rs @@ -675,7 +675,7 @@ where E: EthSpec, { type Output = InboundOutput; - type Error = RPCError; + type Error = (Protocol, RPCError); type Future = BoxFuture<'static, Result>; fn upgrade_inbound(self, socket: TSocket, protocol: ProtocolId) -> Self::Future { @@ -717,10 +717,12 @@ where ) .await { - Err(e) => Err(RPCError::from(e)), + Err(e) => Err((versioned_protocol.protocol(), RPCError::from(e))), Ok((Some(Ok(request)), stream)) => Ok((request, stream)), - Ok((Some(Err(e)), _)) => Err(e), - Ok((None, _)) => Err(RPCError::IncompleteStream), + Ok((Some(Err(e)), _)) => Err((versioned_protocol.protocol(), e)), + Ok((None, _)) => { + Err((versioned_protocol.protocol(), RPCError::IncompleteStream)) + } } } } diff --git a/beacon_node/lighthouse_network/tests/rpc_tests.rs b/beacon_node/lighthouse_network/tests/rpc_tests.rs index 553cfa6f0d..137136e97e 100644 --- a/beacon_node/lighthouse_network/tests/rpc_tests.rs +++ b/beacon_node/lighthouse_network/tests/rpc_tests.rs @@ -5,8 +5,12 @@ use crate::common::spec_with_all_forks_enabled; use crate::common::{Protocol, build_tracing_subscriber}; use bls::Signature; use fixed_bytes::FixedBytesExtended; +use libp2p::PeerId; use lighthouse_network::rpc::{RequestType, methods::*}; -use lighthouse_network::service::api_types::AppRequestId; +use lighthouse_network::service::api_types::{ + AppRequestId, BlobsByRangeRequestId, BlocksByRangeRequestId, ComponentsByRangeRequestId, + DataColumnsByRangeRequestId, DataColumnsByRangeRequester, RangeRequestId, SyncRequestId, +}; use lighthouse_network::{NetworkEvent, ReportSource, Response}; use ssz::Encode; use ssz_types::{RuntimeVariableList, VariableList}; @@ -1785,3 +1789,157 @@ fn test_active_requests() { } }) } + +// Test that when a node receives an invalid BlocksByRange request exceeding the maximum count, +// it bans the sender. +#[test] +fn test_request_too_large_blocks_by_range() { + let spec = Arc::new(spec_with_all_forks_enabled()); + + test_request_too_large( + AppRequestId::Sync(SyncRequestId::BlocksByRange(BlocksByRangeRequestId { + id: 1, + parent_request_id: ComponentsByRangeRequestId { + id: 1, + requester: RangeRequestId::RangeSync { + chain_id: 1, + batch_id: Epoch::new(1), + }, + }, + })), + RequestType::BlocksByRange(OldBlocksByRangeRequest::new( + 0, + spec.max_request_blocks(ForkName::Base) as u64 + 1, // exceeds the max request defined in the spec. + 1, + )), + ); +} + +// Test that when a node receives an invalid BlobsByRange request exceeding the maximum count, +// it bans the sender. +#[test] +fn test_request_too_large_blobs_by_range() { + let spec = Arc::new(spec_with_all_forks_enabled()); + + let max_request_blobs_count = spec.max_request_blob_sidecars(ForkName::Base) as u64 + / spec.max_blobs_per_block_within_fork(ForkName::Base); + test_request_too_large( + AppRequestId::Sync(SyncRequestId::BlobsByRange(BlobsByRangeRequestId { + id: 1, + parent_request_id: ComponentsByRangeRequestId { + id: 1, + requester: RangeRequestId::RangeSync { + chain_id: 1, + batch_id: Epoch::new(1), + }, + }, + })), + RequestType::BlobsByRange(BlobsByRangeRequest { + start_slot: 0, + count: max_request_blobs_count + 1, // exceeds the max request defined in the spec. + }), + ); +} + +// Test that when a node receives an invalid DataColumnsByRange request exceeding the columns count, +// it bans the sender. +#[test] +fn test_request_too_large_data_columns_by_range() { + test_request_too_large( + AppRequestId::Sync(SyncRequestId::DataColumnsByRange( + DataColumnsByRangeRequestId { + id: 1, + parent_request_id: DataColumnsByRangeRequester::ComponentsByRange( + ComponentsByRangeRequestId { + id: 1, + requester: RangeRequestId::RangeSync { + chain_id: 1, + batch_id: Epoch::new(1), + }, + }, + ), + peer: PeerId::random(), + }, + )), + RequestType::DataColumnsByRange(DataColumnsByRangeRequest { + start_slot: 0, + count: 0, + // exceeds the max request defined in the spec. + columns: vec![0; E::number_of_columns() + 1], + }), + ); +} + +fn test_request_too_large(app_request_id: AppRequestId, request: RequestType) { + // Set up the logging. + let log_level = "debug"; + let enable_logging = true; + let _subscriber = build_tracing_subscriber(log_level, enable_logging); + let rt = Arc::new(Runtime::new().unwrap()); + let spec = Arc::new(spec_with_all_forks_enabled()); + + rt.block_on(async { + let (mut sender, mut receiver) = common::build_node_pair( + Arc::downgrade(&rt), + ForkName::Base, + spec, + Protocol::Tcp, + false, + None, + ) + .await; + + // Build the sender future + let sender_future = async { + loop { + match sender.next_event().await { + NetworkEvent::PeerConnectedOutgoing(peer_id) => { + debug!(?request, %peer_id, "Sending RPC request"); + sender + .send_request(peer_id, app_request_id, request.clone()) + .unwrap(); + } + NetworkEvent::ResponseReceived { + app_request_id, + response, + .. + } => { + debug!(?app_request_id, ?response, "Received response"); + } + NetworkEvent::RPCFailed { error, .. } => { + // This variant should be unreachable, as the receiver doesn't respond with an error when a request exceeds the limit. + debug!(?error, "RPC failed"); + unreachable!(); + } + NetworkEvent::PeerDisconnected(peer_id) => { + // The receiver should disconnect as a result of the invalid request. + debug!(%peer_id, "Peer disconnected"); + // End the test. + return; + } + _ => {} + } + } + } + .instrument(info_span!("Sender")); + + // Build the receiver future + let receiver_future = async { + loop { + if let NetworkEvent::RequestReceived { .. } = receiver.next_event().await { + // This event should be unreachable, as the handler drops the invalid request. + unreachable!(); + } + } + } + .instrument(info_span!("Receiver")); + + tokio::select! { + _ = sender_future => {} + _ = receiver_future => {} + _ = sleep(Duration::from_secs(30)) => { + panic!("Future timed out"); + } + } + }); +} From 691c8cf8e69d4d40c4969c2bc493dc1eed9af99f Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Wed, 18 Feb 2026 15:16:57 +1100 Subject: [PATCH 4/6] Fix duplicate data columns in DataColumnsByRange responses (#8843) Co-Authored-By: Jimmy Chen --- .../network_beacon_processor/rpc_methods.rs | 5 +- .../src/network_beacon_processor/tests.rs | 116 ++++++++++++++++++ 2 files changed, 120 insertions(+), 1 deletion(-) diff --git a/beacon_node/network/src/network_beacon_processor/rpc_methods.rs b/beacon_node/network/src/network_beacon_processor/rpc_methods.rs index 5edd661bb6..279870d444 100644 --- a/beacon_node/network/src/network_beacon_processor/rpc_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/rpc_methods.rs @@ -977,7 +977,10 @@ impl NetworkBeaconProcessor { }; // remove all skip slots i.e. duplicated roots - Ok(block_roots.into_iter().unique().collect::>()) + Ok(block_roots + .into_iter() + .unique_by(|(root, _)| *root) + .collect::>()) } /// Handle a `BlobsByRange` request from the peer. diff --git a/beacon_node/network/src/network_beacon_processor/tests.rs b/beacon_node/network/src/network_beacon_processor/tests.rs index 49b1c0c262..32ca84453a 100644 --- a/beacon_node/network/src/network_beacon_processor/tests.rs +++ b/beacon_node/network/src/network_beacon_processor/tests.rs @@ -120,6 +120,39 @@ impl TestRig { .await } + pub async fn new_with_skip_slots(chain_length: u64, skip_slots: &HashSet) -> Self { + let mut spec = test_spec::(); + spec.shard_committee_period = 2; + let spec = Arc::new(spec); + let beacon_processor_config = BeaconProcessorConfig::default(); + let harness = BeaconChainHarness::builder(MainnetEthSpec) + .spec(spec.clone()) + .deterministic_keypairs(VALIDATOR_COUNT) + .fresh_ephemeral_store() + .mock_execution_layer() + .node_custody_type(NodeCustodyType::Fullnode) + .chain_config(<_>::default()) + .build(); + + harness.advance_slot(); + + for slot in 1..=chain_length { + if !skip_slots.contains(&slot) { + harness + .extend_chain( + 1, + BlockStrategy::OnCanonicalHead, + AttestationStrategy::AllValidators, + ) + .await; + } + + harness.advance_slot(); + } + + Self::from_harness(harness, beacon_processor_config, spec).await + } + pub async fn new_parametric( chain_length: u64, beacon_processor_config: BeaconProcessorConfig, @@ -150,6 +183,14 @@ impl TestRig { harness.advance_slot(); } + Self::from_harness(harness, beacon_processor_config, spec).await + } + + async fn from_harness( + harness: BeaconChainHarness, + beacon_processor_config: BeaconProcessorConfig, + spec: Arc, + ) -> Self { let head = harness.chain.head_snapshot(); assert_eq!( @@ -1986,3 +2027,78 @@ async fn test_data_columns_by_range_request_only_returns_requested_columns() { "Should have received at least some data columns" ); } + +/// Test that DataColumnsByRange does not return duplicate data columns for skip slots. +/// +/// When skip slots occur, `forwards_iter_block_roots` returns the same block root for +/// consecutive slots. The deduplication in `get_block_roots_from_store` must use +/// `unique_by` on the root (not the full `(root, slot)` tuple) to avoid serving +/// duplicate data columns for the same block. +#[tokio::test] +async fn test_data_columns_by_range_no_duplicates_with_skip_slots() { + if test_spec::().fulu_fork_epoch.is_none() { + return; + }; + + // Build a chain of 128 slots (4 epochs) with skip slots at positions 5 and 6. + // After 4 epochs, finalized_epoch=2 (finalized_slot=64). Requesting slots 0-9 + // satisfies req_start_slot + req_count <= finalized_slot (10 <= 64), which routes + // through `get_block_roots_from_store` — the code path with the bug. + let skip_slots: HashSet = [5, 6].into_iter().collect(); + let mut rig = TestRig::new_with_skip_slots(128, &skip_slots).await; + + let all_custody_columns = rig.chain.custody_columns_for_epoch(Some(Epoch::new(0))); + let requested_column = vec![all_custody_columns[0]]; + + // Request a range that spans the skip slots (slots 0 through 9). + let start_slot = 0; + let slot_count = 10; + + rig.network_beacon_processor + .send_data_columns_by_range_request( + PeerId::random(), + InboundRequestId::new_unchecked(42, 24), + DataColumnsByRangeRequest { + start_slot, + count: slot_count, + columns: requested_column.clone(), + }, + ) + .unwrap(); + + // Collect block roots from all data column responses. + let mut block_roots: Vec = Vec::new(); + + while let Some(next) = rig.network_rx.recv().await { + if let NetworkMessage::SendResponse { + peer_id: _, + response: Response::DataColumnsByRange(data_column), + inbound_request_id: _, + } = next + { + if let Some(column) = data_column { + block_roots.push(column.block_root()); + } else { + break; + } + } else { + panic!("unexpected message {:?}", next); + } + } + + assert!( + !block_roots.is_empty(), + "Should have received at least some data columns" + ); + + // Before the fix, skip slots caused the same block root to appear multiple times + // (once per skip slot) because .unique() on (Hash256, Slot) tuples didn't deduplicate. + let unique_roots: HashSet<_> = block_roots.iter().collect(); + assert_eq!( + block_roots.len(), + unique_roots.len(), + "Response contained duplicate block roots: got {} columns but only {} unique roots", + block_roots.len(), + unique_roots.len(), + ); +} From c5b4580e37b44a57605a07a9acdd5057c1b06010 Mon Sep 17 00:00:00 2001 From: Pawan Dhananjay Date: Wed, 18 Feb 2026 09:47:07 +0530 Subject: [PATCH 5/6] Return correct variant for snappy errors (#8841) N/A Handle snappy crate errors as InvalidData instead of IoError. Co-Authored-By: Pawan Dhananjay --- .../lighthouse_network/src/rpc/codec.rs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/beacon_node/lighthouse_network/src/rpc/codec.rs b/beacon_node/lighthouse_network/src/rpc/codec.rs index 36d9726dd9..d1a3182fad 100644 --- a/beacon_node/lighthouse_network/src/rpc/codec.rs +++ b/beacon_node/lighthouse_network/src/rpc/codec.rs @@ -457,6 +457,9 @@ fn handle_error( Ok(None) } } + // All snappy errors from the snap crate bubble up as `Other` kind errors + // that imply invalid response + ErrorKind::Other => Err(RPCError::InvalidData(err.to_string())), _ => Err(RPCError::from(err)), } } @@ -2317,4 +2320,43 @@ mod tests { RPCError::InvalidData(_) )); } + + /// Test invalid snappy response. + #[test] + fn test_invalid_snappy_response() { + let spec = spec_with_all_forks_enabled(); + let fork_ctx = Arc::new(fork_context(ForkName::latest(), &spec)); + let max_packet_size = spec.max_payload_size as usize; // 10 MiB. + + let protocol = ProtocolId::new(SupportedProtocol::BlocksByRangeV2, Encoding::SSZSnappy); + + let mut codec = SSZSnappyOutboundCodec::::new( + protocol.clone(), + max_packet_size, + fork_ctx.clone(), + ); + + let mut payload = BytesMut::new(); + payload.extend_from_slice(&[0u8]); + let deneb_epoch = spec.deneb_fork_epoch.unwrap(); + payload.extend_from_slice(&fork_ctx.context_bytes(deneb_epoch)); + + // Claim the MAXIMUM allowed size (10 MiB) + let claimed_size = max_packet_size; + let mut uvi_codec: Uvi = Uvi::default(); + uvi_codec.encode(claimed_size, &mut payload).unwrap(); + payload.extend_from_slice(&[0xBB; 16]); // Junk snappy. + + let result = codec.decode(&mut payload); + + assert!(result.is_err(), "Expected decode to fail"); + + // IoError = reached snappy decode (allocation happened). + let err = result.unwrap_err(); + assert!( + matches!(err, RPCError::InvalidData(_)), + "Should return invalid data variant {}", + err + ); + } } From be799cb2ad2fb0243cdc2a2f368091ffee29fe8e Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Wed, 18 Feb 2026 16:28:17 +1100 Subject: [PATCH 6/6] Validator client head monitor timeout fix (#8846) Fix a bug in v8.1.0 whereby the VC times out continuously with: > Feb 18 02:03:48.030 WARN Head service failed retrying starting next slot error: "Head monitoring stream error, node: 0, error: SseClient(Transport(reqwest::Error { kind: Decode, source: reqwest::Error { kind: Body, source: TimedOut } }))" - Remove the existing timeout for the events API by using `Duration::MAX`. This is necessary as the client is configured with a default timeout. This is the only way to override/remove it. - DO NOT add a `read_timeout` (yet), as this would need to be configured on a per-client basis. We do not want to create a new Client for every call as the early commits on this branch were doing, as this would bypass the TLS cert config, and is also wasteful. Co-Authored-By: hopinheimer Co-Authored-By: Michael Sproul Co-Authored-By: Michael Sproul --- common/eth2/src/lib.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index 10382b028a..76b05130d7 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -76,8 +76,6 @@ const HTTP_GET_BEACON_BLOCK_SSZ_TIMEOUT_QUOTIENT: u32 = 4; const HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT: u32 = 4; const HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT: u32 = 4; const HTTP_GET_VALIDATOR_BLOCK_TIMEOUT_QUOTIENT: u32 = 4; -// Generally the timeout for events should be longer than a slot. -const HTTP_GET_EVENTS_TIMEOUT_MULTIPLIER: u32 = 50; const HTTP_DEFAULT_TIMEOUT_QUOTIENT: u32 = 4; /// A struct to define a variety of different timeouts for different validator tasks to ensure @@ -98,7 +96,6 @@ pub struct Timeouts { pub get_debug_beacon_states: Duration, pub get_deposit_snapshot: Duration, pub get_validator_block: Duration, - pub events: Duration, pub default: Duration, } @@ -119,7 +116,6 @@ impl Timeouts { get_debug_beacon_states: timeout, get_deposit_snapshot: timeout, get_validator_block: timeout, - events: HTTP_GET_EVENTS_TIMEOUT_MULTIPLIER * timeout, default: timeout, } } @@ -142,7 +138,6 @@ impl Timeouts { get_debug_beacon_states: base_timeout / HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT, get_deposit_snapshot: base_timeout / HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT, get_validator_block: base_timeout / HTTP_GET_VALIDATOR_BLOCK_TIMEOUT_QUOTIENT, - events: HTTP_GET_EVENTS_TIMEOUT_MULTIPLIER * base_timeout, default: base_timeout / HTTP_DEFAULT_TIMEOUT_QUOTIENT, } } @@ -2805,10 +2800,14 @@ impl BeaconNodeHttpClient { .join(","); path.query_pairs_mut().append_pair("topics", &topic_string); + // Do not use a timeout for the events endpoint. Using a regular timeout will trigger a + // timeout every `timeout` seconds, regardless of any data streamed from the endpoint. + // In future we could add a read_timeout, but that can only be configured globally on the + // Client. let mut es = self .client .get(path) - .timeout(self.timeouts.events) + .timeout(Duration::MAX) .eventsource() .map_err(Error::SseEventSource)?; // If we don't await `Event::Open` here, then the consumer