Skip to main content

sui_core/
authority_client.rs

1// Copyright (c) 2021, Facebook, Inc. and its affiliates
2// Copyright (c) Mysten Labs, Inc.
3// SPDX-License-Identifier: Apache-2.0
4
5use 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    /// Submits a transaction to validators for sequencing and execution.
39    async fn submit_transaction(
40        &self,
41        request: SubmitTxRequest,
42        client_addr: Option<SocketAddr>,
43    ) -> Result<SubmitTxResponse, SuiError>;
44
45    /// Waits for effects of a transaction that has been submitted to the network
46    /// through the `submit_transaction` API.
47    async fn wait_for_effects(
48        &self,
49        request: WaitForEffectsRequest,
50        client_addr: Option<SocketAddr>,
51    ) -> Result<WaitForEffectsResponse, SuiError>;
52
53    // TODO(fastpath): Add a soft bundle path for mfp which will return the list of consensus positions
54
55    /// Handle Object information requests for this account.
56    async fn handle_object_info_request(
57        &self,
58        request: ObjectInfoRequest,
59    ) -> Result<ObjectInfoResponse, SuiError>;
60
61    /// Handle Object information requests for this account.
62    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    // This API is exclusively used by the benchmark code.
78    // Hence it's OK to return a fixed system state type.
79    async fn handle_system_state_object(
80        &self,
81        request: SystemStateRequest,
82    ) -> Result<SuiSystemState, SuiError>;
83
84    /// Get validator health metrics (for latency measurement)
85    async fn validator_health(
86        &self,
87        request: ValidatorHealthRequest,
88    ) -> Result<ValidatorHealthResponse, SuiError>;
89
90    /// Force the underlying transport to drop any cached connection and establish a fresh one on
91    /// the next request. Used to recover from a connection that has silently gone dead (e.g. the
92    /// peer validator restarted) and is no longer detected as broken by the transport layer.
93    /// Implementations may rate-limit reconnections and ignore the request during the cooldown.
94    /// Default is a no-op for client implementations without a reconnectable transport.
95    fn reconnect(&self) {}
96}
97
98/// Builds a fresh lazy channel; used to re-establish a connection that has gone dead.
99type 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    /// The current (lazy) gRPC client, swappable so a dead connection can be replaced in place.
106    client: Arc<ArcSwap<SuiResult<ValidatorClient<Channel>>>>,
107    /// When present, rebuilds a fresh channel on `reconnect()`. Absent for clients constructed
108    /// directly from a `Channel` (e.g. tests), where reconnection is not possible.
109    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    /// Construct a client whose connection can be re-established via `reconnect()`. The builder is
151    /// invoked once to create the initial lazy channel and again on each reconnect.
152    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    /// Submits a transaction to the Sui network for certification and execution.
175    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    /// Handle Object information requests for this account.
220    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    /// Handle Object information requests for this account.
232    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    /// Handle Object information requests for this account.
244    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        // Build a fresh lazy channel on demand so a dead connection (e.g. after the peer
309        // validator restarts) can be re-established via NetworkAuthorityClient::reconnect().
310        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}