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,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
53130impl < 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.
183307fn 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`.
209331fn spawn_broadcast < F , Fut > ( operation : & ' static str , remotes : Vec < Arc < RemoteBeaconNode > > , attempt : F )
210332where
211333 F : Fn ( Arc < RemoteBeaconNode > ) -> Fut + Send + ' static ,
0 commit comments