Skip to content

Commit 17a3f63

Browse files
committed
wip
1 parent 68c001e commit 17a3f63

10 files changed

Lines changed: 834 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: 137 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,135 @@ 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+
pub async fn attester_duties_at_slots(
77+
&self,
78+
slots: Range<Slot>,
79+
validator_indices: &[ValidatorIndex],
80+
) -> Result<AttesterDuties> {
81+
let epoch = helper_functions::misc::compute_epoch_at_slot::<P>(slots.start);
82+
83+
if let Some(node) = &self.local_node {
84+
match node
85+
.attester_duties_at_slots(slots, validator_indices)
86+
.await
87+
{
88+
Ok(duties) => return Ok(duties),
89+
Err(error) if self.remote_nodes.is_empty() => return Err(error),
90+
Err(error) => {
91+
warn_with_peers!(
92+
"{node} beacon node failed to produce attester duties for epoch \
93+
{epoch}: {error:?}",
94+
);
95+
}
96+
}
97+
}
98+
99+
self.remote_attester_duties(epoch, validator_indices).await
100+
}
101+
102+
async fn remote_attester_duties(
103+
&self,
104+
epoch: Epoch,
105+
validator_indices: &[ValidatorIndex],
106+
) -> Result<AttesterDuties> {
107+
let operation = format!("produce attester duties for epoch {epoch}");
108+
let mut attempts = Vec::with_capacity(self.remote_nodes.len());
109+
110+
for node in &self.remote_nodes {
111+
attempts.push((
112+
node.as_ref(),
113+
BeaconNodeApi::<P>::attester_duties(node.as_ref(), epoch, validator_indices),
114+
));
115+
}
116+
117+
let (node, duties) = first_success(&operation, attempts).await?;
118+
119+
debug_with_peers!(
120+
"{node} beacon node produced {} attester duties for epoch {epoch} \
121+
under dependent root {:?}",
122+
duties.duties.len(),
123+
duties.dependent_root,
124+
);
125+
126+
Ok(duties)
127+
}
51128
}
52129

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

68197
return Ok(data);
69198
}
199+
Err(error) if self.remote_nodes.is_empty() => return Err(error),
70200
Err(error) => {
71201
warn_with_peers!("{node} beacon node failed to {operation}: {error:?}");
72202
}
@@ -144,8 +274,7 @@ impl<P: Preset, W: Wait + Sync> BeaconNodeApi<P> for BeaconNodes<P, W> {
144274
if !subscribe_on.is_empty() {
145275
let subscriptions = Arc::new(subscriptions.to_vec());
146276

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.
277+
// Every node is told, as any of them may be asked to produce or aggregate later.
149278
spawn_broadcast(
150279
"update beacon committee subscriptions",
151280
subscribe_on,
@@ -174,12 +303,7 @@ impl<P: Preset, W: Wait + Sync> BeaconNodeApi<P> for BeaconNodes<P, W> {
174303
}
175304
}
176305

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.
306+
// Detached as a whole rather than per node, so that the nodes are still tried in order.
183307
fn spawn_publish<P: Preset>(
184308
remotes: Vec<Arc<RemoteBeaconNode>>,
185309
attestations: Vec<OwnAttestation<P>>,
@@ -203,9 +327,7 @@ fn spawn_publish<P: Preset>(
203327
});
204328
}
205329

206-
/// Runs `attempt` against every remote node, without holding up the caller.
207-
///
208-
/// Detached for the same reason as [`spawn_publish`].
330+
// Detached for the same reason as `spawn_publish`.
209331
fn spawn_broadcast<F, Fut>(operation: &'static str, remotes: Vec<Arc<RemoteBeaconNode>>, attempt: F)
210332
where
211333
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)