1- use core:: { convert:: identity, fmt:: Display , future:: Future } ;
1+ use core:: { convert:: identity, fmt:: Display , future:: Future , ops :: Range } ;
22use std:: sync:: Arc ;
33
44use 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
2020use 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
2528pub 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
53131impl < 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.
183308fn 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`.
209332fn spawn_broadcast < F , Fut > ( operation : & ' static str , remotes : Vec < Arc < RemoteBeaconNode > > , attempt : F )
210333where
211334 F : Fn ( Arc < RemoteBeaconNode > ) -> Fut + Send + ' static ,
0 commit comments