Skip to content

Commit df506bb

Browse files
committed
wip
1 parent 68c001e commit df506bb

10 files changed

Lines changed: 871 additions & 259 deletions

File tree

http_api_utils/src/response.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ impl<T, M, F> EthResponse<T, M, F> {
113113
self.data
114114
}
115115

116+
#[must_use]
117+
pub fn into_data_and_dependent_root(self) -> (T, Option<H256>) {
118+
(self.data, self.dependent_root)
119+
}
120+
116121
#[must_use]
117122
pub const fn version(mut self, phase: Phase) -> Self {
118123
self.version = Some(phase);

validator/src/beacon_node_api.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,40 @@
11
use core::future::Future;
22

33
use anyhow::Result;
4+
use http_api_utils::ValidatorAttesterDutyResponse;
45
use p2p::BeaconCommitteeSubscription;
56
use types::{
67
nonstandard::OwnAttestation,
78
phase0::{
89
containers::AttestationData,
9-
primitives::{CommitteeIndex, Slot},
10+
primitives::{CommitteeIndex, Epoch, H256, Slot, ValidatorIndex},
1011
},
1112
preset::Preset,
1213
};
1314

15+
pub struct AttesterDuties {
16+
pub dependent_root: H256,
17+
pub duties: Vec<ValidatorAttesterDutyResponse>,
18+
}
19+
1420
/// A beacon node the validator can perform duties against.
15-
///
16-
/// Implementors also implement `Display`, which names them in logs.
1721
pub trait BeaconNodeApi<P: Preset> {
22+
// A remote beacon node reports the dependent root only alongside duties, so `validator_index`
23+
// names the one validator whose duties are requested to obtain it. The built-in node derives
24+
// the root without asking anything and is given `None`.
25+
fn dependent_root(
26+
&self,
27+
epoch: Epoch,
28+
validator_index: Option<ValidatorIndex>,
29+
) -> impl Future<Output = Result<H256>> + Send;
30+
31+
/// <https://ethereum.github.io/beacon-APIs/#/Validator/getAttesterDuties>
32+
fn attester_duties(
33+
&self,
34+
epoch: Epoch,
35+
validator_indices: &[ValidatorIndex],
36+
) -> impl Future<Output = Result<AttesterDuties>> + Send;
37+
1838
/// <https://ethereum.github.io/beacon-APIs/#/Validator/produceAttestationData>
1939
fn attestation_data(
2040
&self,

validator/src/beacon_nodes.rs

Lines changed: 138 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use core::{convert::identity, fmt::Display, future::Future};
1+
use core::{convert::identity, fmt::Display, future::Future, ops::Range};
22
use std::sync::Arc;
33

44
use anyhow::{Error as AnyhowError, Result};
@@ -12,14 +12,17 @@ use types::{
1212
nonstandard::{OwnAttestation, PublishedDuty},
1313
phase0::{
1414
containers::AttestationData,
15-
primitives::{CommitteeIndex, Slot},
15+
primitives::{CommitteeIndex, Epoch, H256, Slot, ValidatorIndex},
1616
},
1717
preset::Preset,
1818
};
1919

2020
use crate::{
21-
beacon_node_api::BeaconNodeApi, local_beacon_node::LocalBeaconNode,
22-
remote_beacon_node::RemoteBeaconNode, remote_beacon_nodes::RemoteBeaconNodes,
21+
beacon_node_api::{AttesterDuties, BeaconNodeApi},
22+
local_beacon_node::LocalBeaconNode,
23+
misc,
24+
remote_beacon_node::RemoteBeaconNode,
25+
remote_beacon_nodes::RemoteBeaconNodes,
2326
};
2427

2528
pub struct BeaconNodes<P: Preset, W: Wait> {
@@ -48,9 +51,136 @@ impl<P: Preset, W: Wait + Sync> BeaconNodes<P, W> {
4851
.map(ArcExt::clone_arc)
4952
.collect()
5053
}
54+
55+
#[must_use]
56+
pub const fn has_local_node(&self) -> bool {
57+
self.local_node.is_some()
58+
}
59+
60+
// The built-in beacon node computes only what is about to be needed; a remote one answers a
61+
// whole epoch per request, so nothing is gained by asking for less than the epochs in flight.
62+
#[must_use]
63+
pub fn prefetch_slots(&self, current_slot: Slot) -> Range<Slot> {
64+
if self.has_local_node() {
65+
return misc::slots_to_compute_in_advance(current_slot);
66+
}
67+
68+
let current_epoch = helper_functions::misc::compute_epoch_at_slot::<P>(current_slot);
69+
70+
helper_functions::misc::compute_start_slot_at_epoch::<P>(current_epoch)
71+
..helper_functions::misc::compute_start_slot_at_epoch::<P>(
72+
current_epoch.saturating_add(2),
73+
)
74+
}
75+
76+
// `slots` must lie within one epoch, as the duties carry a single dependent root.
77+
pub async fn attester_duties_at_slots(
78+
&self,
79+
slots: Range<Slot>,
80+
validator_indices: &[ValidatorIndex],
81+
) -> Result<AttesterDuties> {
82+
let epoch = helper_functions::misc::compute_epoch_at_slot::<P>(slots.start);
83+
84+
if let Some(node) = &self.local_node {
85+
match node
86+
.attester_duties_at_slots(slots, validator_indices)
87+
.await
88+
{
89+
Ok(duties) => return Ok(duties),
90+
Err(error) if self.remote_nodes.is_empty() => return Err(error),
91+
Err(error) => {
92+
warn_with_peers!(
93+
"{node} beacon node failed to produce attester duties for epoch \
94+
{epoch}: {error:?}",
95+
);
96+
}
97+
}
98+
}
99+
100+
self.remote_attester_duties(epoch, validator_indices).await
101+
}
102+
103+
async fn remote_attester_duties(
104+
&self,
105+
epoch: Epoch,
106+
validator_indices: &[ValidatorIndex],
107+
) -> Result<AttesterDuties> {
108+
let operation = format!("produce attester duties for epoch {epoch}");
109+
let mut attempts = Vec::with_capacity(self.remote_nodes.len());
110+
111+
for node in &self.remote_nodes {
112+
attempts.push((
113+
node.as_ref(),
114+
BeaconNodeApi::<P>::attester_duties(node.as_ref(), epoch, validator_indices),
115+
));
116+
}
117+
118+
let (node, duties) = first_success(&operation, attempts).await?;
119+
120+
debug_with_peers!(
121+
"{node} beacon node produced {} attester duties for epoch {epoch} \
122+
under dependent root {:?}",
123+
duties.duties.len(),
124+
duties.dependent_root,
125+
);
126+
127+
Ok(duties)
128+
}
51129
}
52130

53131
impl<P: Preset, W: Wait + Sync> BeaconNodeApi<P> for BeaconNodes<P, W> {
132+
async fn dependent_root(
133+
&self,
134+
epoch: Epoch,
135+
validator_index: Option<ValidatorIndex>,
136+
) -> Result<H256> {
137+
let operation = format!("produce the dependent root of epoch {epoch}");
138+
139+
if let Some(node) = &self.local_node {
140+
match node.dependent_root(epoch, validator_index).await {
141+
Ok(dependent_root) => return Ok(dependent_root),
142+
Err(error) if self.remote_nodes.is_empty() => return Err(error),
143+
Err(error) => {
144+
warn_with_peers!("{node} beacon node failed to {operation}: {error:?}");
145+
}
146+
}
147+
}
148+
149+
let mut attempts = Vec::with_capacity(self.remote_nodes.len());
150+
151+
for node in &self.remote_nodes {
152+
attempts.push((
153+
node.as_ref(),
154+
BeaconNodeApi::<P>::dependent_root(node.as_ref(), epoch, validator_index),
155+
));
156+
}
157+
158+
first_success(&operation, attempts)
159+
.await
160+
.map(|(_, dependent_root)| dependent_root)
161+
}
162+
163+
async fn attester_duties(
164+
&self,
165+
epoch: Epoch,
166+
validator_indices: &[ValidatorIndex],
167+
) -> Result<AttesterDuties> {
168+
if let Some(node) = &self.local_node {
169+
match node.attester_duties(epoch, validator_indices).await {
170+
Ok(duties) => return Ok(duties),
171+
Err(error) if self.remote_nodes.is_empty() => return Err(error),
172+
Err(error) => {
173+
warn_with_peers!(
174+
"{node} beacon node failed to produce attester duties for epoch \
175+
{epoch}: {error:?}",
176+
);
177+
}
178+
}
179+
}
180+
181+
self.remote_attester_duties(epoch, validator_indices).await
182+
}
183+
54184
async fn attestation_data(
55185
&self,
56186
slot: Slot,
@@ -67,6 +197,7 @@ impl<P: Preset, W: Wait + Sync> BeaconNodeApi<P> for BeaconNodes<P, W> {
67197

68198
return Ok(data);
69199
}
200+
Err(error) if self.remote_nodes.is_empty() => return Err(error),
70201
Err(error) => {
71202
warn_with_peers!("{node} beacon node failed to {operation}: {error:?}");
72203
}
@@ -144,8 +275,7 @@ impl<P: Preset, W: Wait + Sync> BeaconNodeApi<P> for BeaconNodes<P, W> {
144275
if !subscribe_on.is_empty() {
145276
let subscriptions = Arc::new(subscriptions.to_vec());
146277

147-
// Every node is told, not just the first one that accepts: any of them may be asked to
148-
// produce or aggregate later in the slot.
278+
// Every node is told, as any of them may be asked to produce or aggregate later.
149279
spawn_broadcast(
150280
"update beacon committee subscriptions",
151281
subscribe_on,
@@ -174,12 +304,7 @@ impl<P: Preset, W: Wait + Sync> BeaconNodeApi<P> for BeaconNodes<P, W> {
174304
}
175305
}
176306

177-
/// Publishes to the first remote node that accepts, without holding up the caller.
178-
///
179-
/// The walk is detached rather than each node separately, so that the nodes are still tried in
180-
/// order: deciding whether to fall through to the next one needs the previous one's result.
181-
/// Attestations are published from the validator's main loop, so awaiting the walk there would let
182-
/// a single unresponsive node delay every later duty in the slot.
307+
// Detached as a whole rather than per node, so that the nodes are still tried in order.
183308
fn spawn_publish<P: Preset>(
184309
remotes: Vec<Arc<RemoteBeaconNode>>,
185310
attestations: Vec<OwnAttestation<P>>,
@@ -203,9 +328,7 @@ fn spawn_publish<P: Preset>(
203328
});
204329
}
205330

206-
/// Runs `attempt` against every remote node, without holding up the caller.
207-
///
208-
/// Detached for the same reason as [`spawn_publish`].
331+
// Detached for the same reason as `spawn_publish`.
209332
fn spawn_broadcast<F, Fut>(operation: &'static str, remotes: Vec<Arc<RemoteBeaconNode>>, attempt: F)
210333
where
211334
F: Fn(Arc<RemoteBeaconNode>) -> Fut + Send + 'static,

validator/src/health.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ impl Health {
1515
matches!(self, Self::Ready)
1616
}
1717

18-
/// A degraded node is still worth asking, but an [`Self::Incompatible`] one would answer under
19-
/// the wrong domain and an [`Self::Unreachable`] one would only burn the deadline.
18+
// A degraded node is still worth asking, unlike one on the wrong network or an unreachable one.
2019
#[must_use]
2120
pub const fn can_serve(self) -> bool {
2221
matches!(self, Self::Unusable | Self::Unknown | Self::Ready)

0 commit comments

Comments
 (0)