1use anyhow::anyhow;
6use arc_swap::ArcSwap;
7use async_trait::async_trait;
8use mysten_network::config::Config;
9use parking_lot::Mutex;
10use std::collections::BTreeMap;
11use std::net::SocketAddr;
12use std::sync::Arc;
13use std::time::{Duration, Instant};
14use sui_network::{api::ValidatorClient, tonic};
15use sui_types::base_types::AuthorityName;
16use sui_types::committee::CommitteeWithNetworkMetadata;
17use sui_types::crypto::NetworkPublicKey;
18use sui_types::error::{SuiError, SuiResult};
19use sui_types::messages_checkpoint::{
20 CheckpointRequest, CheckpointRequestV2, CheckpointResponse, CheckpointResponseV2,
21};
22use sui_types::multiaddr::Multiaddr;
23use sui_types::sui_system_state::SuiSystemState;
24use tap::TapFallible;
25
26use crate::authority_client::tonic::IntoRequest;
27use sui_network::tonic::metadata::KeyAndValueRef;
28use sui_network::tonic::transport::Channel;
29use sui_types::messages_grpc::{
30 ObjectInfoRequest, ObjectInfoResponse, RawValidatorHealthRequest, RawWaitForEffectsRequest,
31 SubmitTxRequest, SubmitTxResponse, SystemStateRequest, TransactionInfoRequest,
32 TransactionInfoResponse, ValidatorHealthRequest, ValidatorHealthResponse,
33 WaitForEffectsRequest, WaitForEffectsResponse,
34};
35
36#[async_trait]
37pub trait AuthorityAPI {
38 async fn submit_transaction(
40 &self,
41 request: SubmitTxRequest,
42 client_addr: Option<SocketAddr>,
43 ) -> Result<SubmitTxResponse, SuiError>;
44
45 async fn wait_for_effects(
48 &self,
49 request: WaitForEffectsRequest,
50 client_addr: Option<SocketAddr>,
51 ) -> Result<WaitForEffectsResponse, SuiError>;
52
53 async fn handle_object_info_request(
57 &self,
58 request: ObjectInfoRequest,
59 ) -> Result<ObjectInfoResponse, SuiError>;
60
61 async fn handle_transaction_info_request(
63 &self,
64 request: TransactionInfoRequest,
65 ) -> Result<TransactionInfoResponse, SuiError>;
66
67 async fn handle_checkpoint(
68 &self,
69 request: CheckpointRequest,
70 ) -> Result<CheckpointResponse, SuiError>;
71
72 async fn handle_checkpoint_v2(
73 &self,
74 request: CheckpointRequestV2,
75 ) -> Result<CheckpointResponseV2, SuiError>;
76
77 async fn handle_system_state_object(
80 &self,
81 request: SystemStateRequest,
82 ) -> Result<SuiSystemState, SuiError>;
83
84 async fn validator_health(
86 &self,
87 request: ValidatorHealthRequest,
88 ) -> Result<ValidatorHealthResponse, SuiError>;
89
90 fn reconnect(&self) {}
96}
97
98type ChannelBuilder = Arc<dyn Fn() -> SuiResult<Channel> + Send + Sync>;
100
101const RECONNECT_COOLDOWN: Duration = Duration::from_secs(2);
102
103#[derive(Clone)]
104pub struct NetworkAuthorityClient {
105 client: Arc<ArcSwap<SuiResult<ValidatorClient<Channel>>>>,
107 builder: Option<ChannelBuilder>,
110 last_reconnect: Arc<Mutex<Option<Instant>>>,
111}
112
113impl NetworkAuthorityClient {
114 pub async fn connect(
115 address: &Multiaddr,
116 tls_target: NetworkPublicKey,
117 ) -> anyhow::Result<Self> {
118 let tls_config = sui_tls::create_rustls_client_config(
119 tls_target,
120 sui_tls::SUI_VALIDATOR_SERVER_NAME.to_string(),
121 None,
122 );
123 let channel = mysten_network::client::connect(address, tls_config)
124 .await
125 .map_err(|err| anyhow!(err.to_string()))?;
126 Ok(Self::new(channel))
127 }
128
129 pub fn connect_lazy(address: &Multiaddr, tls_target: NetworkPublicKey) -> Self {
130 let address = address.clone();
131 Self::new_reconnectable(move || {
132 let tls_config = sui_tls::create_rustls_client_config(
133 tls_target.clone(),
134 sui_tls::SUI_VALIDATOR_SERVER_NAME.to_string(),
135 None,
136 );
137 mysten_network::client::connect_lazy(&address, tls_config)
138 .map_err(|err| err.to_string().into())
139 })
140 }
141
142 pub fn new(channel: Channel) -> Self {
143 Self {
144 client: Arc::new(ArcSwap::from_pointee(Ok(ValidatorClient::new(channel)))),
145 builder: None,
146 last_reconnect: Arc::new(Mutex::new(None)),
147 }
148 }
149
150 pub(crate) fn new_reconnectable(
153 builder: impl Fn() -> SuiResult<Channel> + Send + Sync + 'static,
154 ) -> Self {
155 let initial = builder().map(ValidatorClient::new);
156 Self {
157 client: Arc::new(ArcSwap::from_pointee(initial)),
158 builder: Some(Arc::new(builder)),
159 last_reconnect: Arc::new(Mutex::new(None)),
160 }
161 }
162
163 pub(crate) fn client(&self) -> SuiResult<ValidatorClient<Channel>> {
164 (**self.client.load()).clone()
165 }
166
167 pub fn get_client_for_testing(&self) -> SuiResult<ValidatorClient<Channel>> {
168 self.client()
169 }
170}
171
172#[async_trait]
173impl AuthorityAPI for NetworkAuthorityClient {
174 async fn submit_transaction(
176 &self,
177 request: SubmitTxRequest,
178 client_addr: Option<SocketAddr>,
179 ) -> Result<SubmitTxResponse, SuiError> {
180 let mut request = request.into_raw()?.into_request();
181 insert_metadata(&mut request, client_addr);
182
183 self.client()?
184 .submit_transaction(request)
185 .await
186 .map(tonic::Response::into_inner)
187 .map_err(Into::<SuiError>::into)?
188 .try_into()
189 }
190
191 async fn wait_for_effects(
192 &self,
193 request: WaitForEffectsRequest,
194 client_addr: Option<SocketAddr>,
195 ) -> Result<WaitForEffectsResponse, SuiError> {
196 let raw_request: RawWaitForEffectsRequest = request.try_into()?;
197 let mut request = raw_request.into_request();
198 insert_metadata(&mut request, client_addr);
199
200 self.client()?
201 .wait_for_effects(request)
202 .await
203 .map(tonic::Response::into_inner)
204 .map_err(Into::<SuiError>::into)?
205 .try_into()
206 }
207
208 async fn handle_object_info_request(
209 &self,
210 request: ObjectInfoRequest,
211 ) -> Result<ObjectInfoResponse, SuiError> {
212 self.client()?
213 .object_info(request)
214 .await
215 .map(tonic::Response::into_inner)
216 .map_err(Into::into)
217 }
218
219 async fn handle_transaction_info_request(
221 &self,
222 request: TransactionInfoRequest,
223 ) -> Result<TransactionInfoResponse, SuiError> {
224 self.client()?
225 .transaction_info(request)
226 .await
227 .map(tonic::Response::into_inner)
228 .map_err(Into::into)
229 }
230
231 async fn handle_checkpoint(
233 &self,
234 request: CheckpointRequest,
235 ) -> Result<CheckpointResponse, SuiError> {
236 self.client()?
237 .checkpoint(request)
238 .await
239 .map(tonic::Response::into_inner)
240 .map_err(Into::into)
241 }
242
243 async fn handle_checkpoint_v2(
245 &self,
246 request: CheckpointRequestV2,
247 ) -> Result<CheckpointResponseV2, SuiError> {
248 self.client()?
249 .checkpoint_v2(request)
250 .await
251 .map(tonic::Response::into_inner)
252 .map_err(Into::into)
253 }
254
255 async fn handle_system_state_object(
256 &self,
257 request: SystemStateRequest,
258 ) -> Result<SuiSystemState, SuiError> {
259 self.client()?
260 .get_system_state_object(request)
261 .await
262 .map(tonic::Response::into_inner)
263 .map_err(Into::into)
264 }
265
266 async fn validator_health(
267 &self,
268 request: ValidatorHealthRequest,
269 ) -> Result<ValidatorHealthResponse, SuiError> {
270 let raw_request: RawValidatorHealthRequest = request.try_into()?;
271
272 self.client()?
273 .validator_health(raw_request)
274 .await
275 .map(tonic::Response::into_inner)
276 .map_err(Into::<SuiError>::into)?
277 .try_into()
278 }
279
280 fn reconnect(&self) {
281 let Some(builder) = &self.builder else {
282 return;
283 };
284 let mut last_reconnect = self.last_reconnect.lock();
285 if last_reconnect.is_some_and(|t| t.elapsed() < RECONNECT_COOLDOWN) {
286 return;
287 }
288 let fresh = builder().map(ValidatorClient::new);
289 self.client.store(Arc::new(fresh));
290 *last_reconnect = Some(Instant::now());
291 }
292}
293
294pub fn make_network_authority_clients_with_network_config(
295 committee: &CommitteeWithNetworkMetadata,
296 network_config: &Config,
297) -> BTreeMap<AuthorityName, NetworkAuthorityClient> {
298 let mut authority_clients = BTreeMap::new();
299 for (name, (_state, network_metadata)) in committee.validators() {
300 let address = network_metadata
301 .network_address
302 .clone()
303 .rewrite_udp_to_tcp()
304 .rewrite_http_to_https();
305 let maybe_network_key = network_metadata.network_public_key.clone();
306 let network_config = network_config.clone();
307 let name = *name;
308 let builder = move || -> SuiResult<Channel> {
311 let key = maybe_network_key
312 .clone()
313 .ok_or_else(|| SuiError::from("network public key is not available"))?;
314 let tls_config = sui_tls::create_rustls_client_config(
315 key,
316 sui_tls::SUI_VALIDATOR_SERVER_NAME.to_string(),
317 None,
318 );
319 network_config
320 .connect_lazy(&address, tls_config)
321 .map_err(|e| e.to_string().into())
322 .tap_err(|e| {
323 tracing::error!(
324 address = %address,
325 name = %name,
326 "unable to create authority client: {e}"
327 )
328 })
329 };
330 let client = NetworkAuthorityClient::new_reconnectable(builder);
331 authority_clients.insert(name, client);
332 }
333 authority_clients
334}
335
336pub fn make_authority_clients_with_timeout_config(
337 committee: &CommitteeWithNetworkMetadata,
338 connect_timeout: Duration,
339 request_timeout: Duration,
340) -> BTreeMap<AuthorityName, NetworkAuthorityClient> {
341 let mut network_config = mysten_network::config::Config::new();
342 network_config.connect_timeout = Some(connect_timeout);
343 network_config.request_timeout = Some(request_timeout);
344 network_config.http2_keepalive_interval = Some(connect_timeout);
345 network_config.http2_keepalive_timeout = Some(connect_timeout);
346 make_network_authority_clients_with_network_config(committee, &network_config)
347}
348
349fn insert_metadata<T>(request: &mut tonic::Request<T>, client_addr: Option<SocketAddr>) {
350 if let Some(client_addr) = client_addr {
351 let mut metadata = tonic::metadata::MetadataMap::new();
352 metadata.insert("x-forwarded-for", client_addr.to_string().parse().unwrap());
353 metadata
354 .iter()
355 .for_each(|key_and_value| match key_and_value {
356 KeyAndValueRef::Ascii(key, value) => {
357 request.metadata_mut().insert(key, value.clone());
358 }
359 KeyAndValueRef::Binary(key, value) => {
360 request.metadata_mut().insert_bin(key, value.clone());
361 }
362 });
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369 use std::sync::atomic::{AtomicUsize, Ordering};
370
371 #[tokio::test]
372 async fn test_reconnect_cooldown() {
373 let build_count = Arc::new(AtomicUsize::new(0));
374 let build_count_clone = build_count.clone();
375 let client = NetworkAuthorityClient::new_reconnectable(move || {
376 build_count_clone.fetch_add(1, Ordering::SeqCst);
377 Ok(tonic::transport::Endpoint::from_static("http://127.0.0.1:1").connect_lazy())
378 });
379 assert_eq!(build_count.load(Ordering::SeqCst), 1);
380
381 client.reconnect();
382 assert_eq!(build_count.load(Ordering::SeqCst), 2);
383
384 client.reconnect();
385 assert_eq!(build_count.load(Ordering::SeqCst), 2);
386
387 tokio::time::sleep(RECONNECT_COOLDOWN + Duration::from_millis(100)).await;
388 client.reconnect();
389 assert_eq!(build_count.load(Ordering::SeqCst), 3);
390 }
391}