Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7e8d38d
Eagerly sign sync committee messages on non-optimistic head events
shane-moore Jul 18, 2026
d698f59
Harden sync service loop against slot-boundary clock races
shane-moore Jul 18, 2026
f3716de
Derive the triggered slot from the trigger, not the clock
shane-moore Jul 18, 2026
9f37ed0
Group gnosis sync message due keys together
shane-moore Jul 21, 2026
40db289
Trim comments to constraint statements
shane-moore Jul 21, 2026
4c836b9
Rename head monitor tests to match module convention
shane-moore Jul 21, 2026
909373e
Simplify head event deadline helper
shane-moore Jul 21, 2026
ddb1a93
Merge remote-tracking branch 'upstream/unstable' into feat/eager-sync…
shane-moore Jul 22, 2026
4d7a74e
Merge upstream/unstable into feat/eager-sync-committee-messages
shane-moore Jul 29, 2026
31bb2ac
Merge upstream/unstable into feat/eager-sync-committee-messages
shane-moore Aug 3, 2026
e1b9bb4
Remove dead sync committee task result
shane-moore Aug 4, 2026
257a738
Relax sync committee test timeout
shane-moore Aug 4, 2026
df5a158
Skip sync committee tasks past the contribution deadline
shane-moore Aug 10, 2026
09296ab
Tidy sync committee skip paths
shane-moore Aug 10, 2026
b095400
Treat clock read failure as terminal in head monitor
shane-moore Aug 13, 2026
367bb6f
Clarify clock failure comment in head monitor
shane-moore Aug 17, 2026
ce9403c
Merge upstream/unstable into feat/eager-sync-committee-messages
shane-moore Aug 17, 2026
68355f0
Merge remote-tracking branch 'upstream/unstable' into feat/eager-sync…
shane-moore Aug 24, 2026
cf939e1
Use upstream sync_message_deadline helper in service loop
shane-moore Aug 24, 2026
1542c48
Merge unstable and resolve event channel conflicts
shane-moore Sep 16, 2026
0eaf380
Merge unstable and resolve mock beacon node conflicts
shane-moore Sep 22, 2026
02cd50b
Merge unstable and resolve validator test rig conflicts
shane-moore Sep 28, 2026
5e6c06c
Read sync aggregators at the contribution deadline
shane-moore Sep 28, 2026
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
10 changes: 5 additions & 5 deletions book/src/help_vc.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,11 +187,11 @@ Flags:
validators-dir. Validators will need to be manually added to the
validator_definitions.yml file.
--disable-beacon-head-monitor
Disable the beacon head monitor which tries to attest as soon as any
of the configured beacon nodes sends a head event. Leaving the service
enabled is recommended, but disabling it can lead to reduced bandwidth
and more predictable usage of the primary beacon node (rather than the
fastest BN).
Disable the beacon head monitor which triggers attestations and sync
committee messages when a configured beacon node sends a head event.
Leaving it enabled is recommended, but disabling it can lead to
reduced bandwidth and more predictable usage of the primary beacon
node (rather than the fastest BN).
--disable-latency-measurement-service
Disables the service that periodically attempts to measure latency to
BNs.
Expand Down
121 changes: 118 additions & 3 deletions testing/validator_test_rig/src/mock_beacon_node.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use eth2::types::{
GenericResponse, ProduceBlockV4Response, PublishBlockRequest,
GenericResponse, ProduceBlockV4Response, PublishBlockRequest, RootData,
SignedExecutionPayloadEnvelopeContents, SubmittedBuilderPreferences, SyncingData,
};
use eth2::{BLOB_DATA_INCLUDED_HEADER, BeaconNodeHttpClient, CONSENSUS_VERSION_HEADER, Timeouts};
Expand All @@ -14,9 +14,10 @@ use std::sync::{Arc, Mutex};
use std::time::Duration;
use tracing::info;
use types::{
ChainSpec, ConfigAndPreset, EthSpec, ExecutionPayloadEnvelope, ForkName, Hash256,
ChainSpec, ConfigAndPreset, Epoch, EthSpec, ExecutionPayloadEnvelope, ForkName, Hash256,
PayloadAttestationData, PayloadAttestationMessage, SignedBlindedBeaconBlock,
SignedExecutionPayloadEnvelope, Slot,
SignedContributionAndProof, SignedExecutionPayloadEnvelope, Slot, SyncCommitteeContribution,
SyncCommitteeMessage, SyncDuty,
};

pub struct MockBeaconNode<E: EthSpec> {
Expand All @@ -30,6 +31,8 @@ pub struct MockBeaconNode<E: EthSpec> {
Arc<Mutex<Vec<SignedExecutionPayloadEnvelopeContents<E>>>>,
pub payload_attestation_message: Arc<Mutex<Vec<PayloadAttestationMessage>>>,
pub builder_preferences: Arc<Mutex<Vec<SubmittedBuilderPreferences>>>,
pub sync_committee_messages: Arc<Mutex<Vec<SyncCommitteeMessage>>>,
pub sync_committee_contributions: Arc<Mutex<Vec<SignedContributionAndProof<E>>>>,
}

impl<E: EthSpec> MockBeaconNode<E> {
Expand All @@ -50,6 +53,8 @@ impl<E: EthSpec> MockBeaconNode<E> {
execution_payload_envelope_contents: Arc::new(Mutex::new(Vec::new())),
payload_attestation_message: Arc::new(Mutex::new(Vec::new())),
builder_preferences: Arc::new(Mutex::new(Vec::new())),
sync_committee_messages: Arc::new(Mutex::new(Vec::new())),
sync_committee_contributions: Arc::new(Mutex::new(Vec::new())),
}
}

Expand Down Expand Up @@ -110,6 +115,116 @@ impl<E: EthSpec> MockBeaconNode<E> {
.create();
}

/// Mocks `POST /eth/v1/validator/duties/sync/{epoch}`
pub fn mock_sync_duties(&mut self, epoch: Epoch, duties: Vec<SyncDuty>) -> Mock {
let path_pattern = Regex::new(&format!(
r"^/eth/v1/validator/duties/sync/{}$",
epoch.as_u64()
))
.unwrap();
let response =
GenericResponse::from(duties).add_execution_optimistic_finalized(false, false);

self.server
.mock("POST", Matcher::Regex(path_pattern.to_string()))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(serde_json::to_string(&response).unwrap())
.create()
}

/// Mocks `GET /eth/v1/beacon/blocks/head/root`
pub fn mock_get_head_block_root(&mut self, root: Hash256) -> Mock {
let path_pattern = Regex::new(r"^/eth/v1/beacon/blocks/head/root$").unwrap();
let response = GenericResponse::from(RootData { root })
.add_execution_optimistic_finalized(false, false);

self.server
.mock("GET", Matcher::Regex(path_pattern.to_string()))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(serde_json::to_string(&response).unwrap())
.create()
}

/// Mocks `POST /eth/v1/beacon/pool/sync_committees`
pub fn mock_post_sync_committee_messages(&mut self) -> Mock {
let path_pattern = Regex::new(r"^/eth/v1/beacon/pool/sync_committees$").unwrap();
let sync_committee_messages = Arc::clone(&self.sync_committee_messages);

self.server
.mock("POST", Matcher::Regex(path_pattern.to_string()))
.with_status(200)
.with_body_from_request(move |request| {
let body = request.body().expect("Failed to get request body");
let messages: Vec<SyncCommitteeMessage> = serde_json::from_slice(body)
.expect("Failed to deserialize sync committee messages");
sync_committee_messages.lock().unwrap().extend(messages);
vec![]
})
.create()
}

/// Mocks `GET /eth/v1/validator/sync_committee_contribution`, matching the slot, block root
/// and subcommittee index of `contribution`.
pub fn mock_get_sync_committee_contribution(
&mut self,
contribution: &SyncCommitteeContribution<E>,
) -> Mock {
let path_pattern = Regex::new(r"^/eth/v1/validator/sync_committee_contribution$").unwrap();
let response = GenericResponse::from(contribution.clone());

self.server
.mock("GET", Matcher::Regex(path_pattern.to_string()))
.match_query(Matcher::AllOf(vec![
Matcher::UrlEncoded("slot".into(), contribution.slot.to_string()),
Matcher::UrlEncoded(
"beacon_block_root".into(),
format!("{:?}", contribution.beacon_block_root),
),
Matcher::UrlEncoded(
"subcommittee_index".into(),
contribution.subcommittee_index.to_string(),
),
]))
.with_status(200)
.with_header("content-type", "application/json")
.with_body(serde_json::to_string(&response).unwrap())
.create()
}

/// Mocks `POST /eth/v1/validator/contribution_and_proofs`
pub fn mock_post_contribution_and_proofs(&mut self) -> Mock {
let path_pattern = Regex::new(r"^/eth/v1/validator/contribution_and_proofs$").unwrap();
let sync_committee_contributions = Arc::clone(&self.sync_committee_contributions);

self.server
.mock("POST", Matcher::Regex(path_pattern.to_string()))
.with_status(200)
.with_body_from_request(move |request| {
let body = request.body().expect("Failed to get request body");
let contributions: Vec<SignedContributionAndProof<E>> =
serde_json::from_slice(body)
.expect("Failed to deserialize sync committee contributions");
sync_committee_contributions
.lock()
.unwrap()
.extend(contributions);
vec![]
})
.create()
}

/// Mocks `POST /eth/v1/validator/sync_committee_subscriptions`
pub fn mock_sync_committee_subscriptions(&mut self) -> Mock {
let path_pattern = Regex::new(r"^/eth/v1/validator/sync_committee_subscriptions$").unwrap();

self.server
.mock("POST", Matcher::Regex(path_pattern.to_string()))
.with_status(200)
.create()
}

/// Mocks `POST /eth/v4/validator/blocks/{slot}`, matching the given `include_payload` query
/// value and answering with `response`.
pub fn mock_post_validator_blocks_v4(
Expand Down
1 change: 0 additions & 1 deletion testing/validator_test_rig/src/validator_client_harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ impl ValidatorClientHarness {
config: &ValidatorStoreConfig,
) -> Self {
let spec = Arc::new(spec);

let test_runtime = TestRuntime::default();
let executor = test_runtime.task_executor.clone();
let slot_duration = spec.get_slot_duration();
Expand Down
1 change: 1 addition & 0 deletions validator_client/beacon_node_fallback/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ types = { workspace = true }
validator_metrics = { workspace = true }

[dev-dependencies]
tokio = { workspace = true, features = ["test-util"] }
validator_test_rig = { workspace = true }
Loading
Loading