@@ -127,14 +127,16 @@ pub(crate) trait Execute: ValidateChecksums {
127127 fn response_pre_check_status ( response : & Self :: GrpcResponse ) -> crate :: Result < i32 > ;
128128}
129129
130- struct ExecuteContext {
130+ struct ExecuteContext < ' a > {
131131 // When `Some` the `transaction_id` will be regenerated when expired.
132132 operator_account_id : Option < AccountId > ,
133133 network : Arc < NetworkData > ,
134134 backoff_config : ExponentialBackoff ,
135135 max_attempts : usize ,
136136 // timeout for a single grpc request.
137137 grpc_timeout : Option < Duration > ,
138+ // Reference to the client for triggering network updates
139+ client : & ' a Client ,
138140}
139141
140142pub ( crate ) async fn execute < E > (
@@ -187,24 +189,29 @@ where
187189 operator_account_id,
188190 network : client. net ( ) . 0 . load_full ( ) ,
189191 grpc_timeout : backoff. grpc_timeout ,
192+ client,
190193 } ,
191194 executable,
192195 )
193196 . await
194197}
195198
196- async fn execute_inner < E > ( ctx : & ExecuteContext , executable : & E ) -> crate :: Result < E :: Response >
199+ async fn execute_inner < ' a , E > (
200+ ctx : & ExecuteContext < ' a > ,
201+ executable : & E ,
202+ ) -> crate :: Result < E :: Response >
197203where
198204 E : Execute + Sync ,
199205{
200- fn recurse_ping ( ctx : & ExecuteContext , index : usize ) -> BoxFuture < ' _ , bool > {
206+ fn recurse_ping < ' a , ' b : ' a > ( ctx : & ' b ExecuteContext < ' a > , index : usize ) -> BoxFuture < ' b , bool > {
201207 Box :: pin ( async move {
202208 let ctx = ExecuteContext {
203209 operator_account_id : None ,
204210 network : Arc :: clone ( & ctx. network ) ,
205211 backoff_config : ctx. backoff_config . clone ( ) ,
206212 max_attempts : ctx. max_attempts ,
207213 grpc_timeout : ctx. grpc_timeout ,
214+ client : ctx. client ,
208215 } ;
209216 let ping_query = PingQuery :: new ( ctx. network . node_ids ( ) [ index] ) ;
210217
@@ -350,8 +357,8 @@ fn map_tonic_error(
350357 }
351358}
352359
353- async fn execute_single < E : Execute + Sync > (
354- ctx : & ExecuteContext ,
360+ async fn execute_single < ' a , E : Execute + Sync > (
361+ ctx : & ExecuteContext < ' a > ,
355362 executable : & E ,
356363 node_index : usize ,
357364 transaction_id : & mut Option < TransactionId > ,
@@ -449,6 +456,26 @@ async fn execute_single<E: Execute + Sync>(
449456 ) ) )
450457 }
451458
459+ Status :: InvalidNodeAccountId => {
460+ // The node account is invalid or doesn't match the submitted node
461+ // Mark the node as unhealthy and retry with backoff
462+ // This typically indicates the address book is out of date
463+ ctx. network . mark_node_unhealthy ( node_index) ;
464+
465+ log:: warn!(
466+ "Node at index {node_index} / node id {node_account_id} returned {status:?}, marking unhealthy. Updating address book before retry."
467+ ) ;
468+
469+ // Update the network address book before retrying
470+ ctx. client . update_network_now ( ) . await ;
471+
472+ Err ( retry:: Error :: Transient ( executable. make_error_pre_check (
473+ status,
474+ transaction_id. as_ref ( ) ,
475+ response,
476+ ) ) )
477+ }
478+
452479 _ if executable. should_retry_pre_check ( status) => {
453480 // conditional retry on pre-check should back-off and try again
454481 Err ( retry:: Error :: Transient ( executable. make_error_pre_check (
0 commit comments