Skip to main content

hiero_sdk/client/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use std::borrow::Cow;
4use std::collections::HashMap;
5use std::fmt;
6use std::num::{
7    NonZeroU64,
8    NonZeroUsize,
9};
10use std::sync::atomic::{
11    AtomicBool,
12    AtomicU64,
13    Ordering,
14};
15use std::time::Duration;
16
17pub(crate) use network::{
18    Network,
19    NetworkData,
20};
21pub(crate) use operator::Operator;
22use parking_lot::RwLock;
23use tokio::sync::watch;
24use triomphe::Arc;
25
26use self::network::managed::ManagedNetwork;
27use self::network::mirror::MirrorNetwork;
28pub(crate) use self::network::mirror::MirrorNetworkData;
29use crate::ping_query::PingQuery;
30use crate::signer::AnySigner;
31use crate::{
32    AccountId,
33    ArcSwapOption,
34    Error,
35    Hbar,
36    LedgerId,
37    NodeAddressBook,
38    NodeAddressBookQuery,
39    PrivateKey,
40    PublicKey,
41};
42
43#[cfg(feature = "serde")]
44mod config;
45
46mod network;
47mod operator;
48
49/// Default gRPC deadline for requests and channel connection timeouts
50pub(crate) const DEFAULT_GRPC_DEADLINE: Duration = Duration::from_secs(10);
51
52/// Default request timeout for the entire operation (including retries)
53pub(crate) const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(120);
54
55#[derive(Copy, Clone)]
56pub(crate) struct ClientBackoff {
57    pub(crate) max_backoff: Duration,
58    // min backoff.
59    pub(crate) initial_backoff: Duration,
60    pub(crate) max_attempts: usize,
61    pub(crate) request_timeout: Option<Duration>,
62    pub(crate) grpc_deadline: Duration,
63}
64
65impl Default for ClientBackoff {
66    fn default() -> Self {
67        Self {
68            max_backoff: Duration::from_millis(backoff::default::MAX_INTERVAL_MILLIS),
69            initial_backoff: Duration::from_millis(backoff::default::INITIAL_INTERVAL_MILLIS),
70            max_attempts: 10,
71            request_timeout: Some(DEFAULT_REQUEST_TIMEOUT),
72            grpc_deadline: DEFAULT_GRPC_DEADLINE,
73        }
74    }
75}
76
77// yes, client is complicated enough for this, even if it's only internal.
78struct ClientBuilder {
79    network: ManagedNetwork,
80    operator: Option<Operator>,
81    max_transaction_fee: Option<NonZeroU64>,
82    max_query_payment: Option<NonZeroU64>,
83    ledger_id: Option<LedgerId>,
84    auto_validate_checksums: bool,
85    regenerate_transaction_ids: bool,
86    update_network: bool,
87    backoff: ClientBackoff,
88}
89
90impl ClientBuilder {
91    #[must_use]
92    fn new(network: ManagedNetwork) -> Self {
93        Self {
94            network,
95            operator: None,
96            max_transaction_fee: None,
97            max_query_payment: None,
98            ledger_id: None,
99            auto_validate_checksums: false,
100            regenerate_transaction_ids: true,
101            update_network: true,
102            backoff: ClientBackoff::default(),
103        }
104    }
105
106    fn disable_network_updating(self) -> Self {
107        Self { update_network: false, ..self }
108    }
109
110    fn ledger_id(self, ledger_id: Option<LedgerId>) -> Self {
111        Self { ledger_id, ..self }
112    }
113
114    fn build(self) -> Client {
115        let Self {
116            network,
117            operator,
118            max_transaction_fee,
119            max_query_payment,
120            ledger_id,
121            auto_validate_checksums,
122            regenerate_transaction_ids,
123            update_network,
124            backoff,
125        } = self;
126
127        let network_update_tx = match update_network {
128            true => network::managed::spawn_network_update(
129                network.clone(),
130                Some(Duration::from_secs(24 * 60 * 60)),
131            ),
132            // yeah, we just drop the rx.
133            false => watch::channel(None).0,
134        };
135
136        Client(Arc::new(ClientInner {
137            network,
138            operator: ArcSwapOption::new(operator.map(Arc::new)),
139            max_transaction_fee_tinybar: AtomicU64::new(
140                max_transaction_fee.map_or(0, NonZeroU64::get),
141            ),
142            max_query_payment_tinybar: AtomicU64::new(max_query_payment.map_or(0, NonZeroU64::get)),
143            ledger_id: ArcSwapOption::new(ledger_id.map(Arc::new)),
144            auto_validate_checksums: AtomicBool::new(auto_validate_checksums),
145            regenerate_transaction_ids: AtomicBool::new(regenerate_transaction_ids),
146            enable_receipt_record_query_failover: AtomicBool::new(false),
147            network_update_tx,
148            backoff: RwLock::new(backoff),
149        }))
150    }
151}
152
153struct ClientInner {
154    network: ManagedNetwork,
155    operator: ArcSwapOption<Operator>,
156    max_transaction_fee_tinybar: AtomicU64,
157    max_query_payment_tinybar: AtomicU64,
158    ledger_id: ArcSwapOption<LedgerId>,
159    auto_validate_checksums: AtomicBool,
160    regenerate_transaction_ids: AtomicBool,
161    enable_receipt_record_query_failover: AtomicBool,
162    network_update_tx: watch::Sender<Option<Duration>>,
163    backoff: RwLock<ClientBackoff>,
164}
165
166/// Managed client for use on the Hiero network.
167#[derive(Clone)]
168pub struct Client(Arc<ClientInner>);
169
170impl fmt::Debug for Client {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        // todo: put anything important here.
173        f.debug_struct("Client").finish_non_exhaustive()
174    }
175}
176
177impl Client {
178    #[cfg(feature = "serde")]
179    fn from_config_data(config: config::ClientConfig) -> crate::Result<Self> {
180        let config::ClientConfig { operator, network, mirror_network } = config;
181
182        // fixme: check to ensure net and mirror net are the same when they're a network name (no other SDK actually checks this though)
183        let client = match network {
184            config::Either::Left(network) => Client::for_network(network)?,
185            config::Either::Right(it) => match it {
186                config::NetworkName::Mainnet => Client::for_mainnet(),
187                config::NetworkName::Testnet => Client::for_testnet(),
188                config::NetworkName::Previewnet => Client::for_previewnet(),
189            },
190        };
191
192        let mirror_network = mirror_network.map(|mirror_network| match mirror_network {
193            config::Either::Left(mirror_network) => {
194                MirrorNetwork::from_addresses(mirror_network.into_iter().map(Cow::Owned).collect())
195            }
196            config::Either::Right(it) => match it {
197                config::NetworkName::Mainnet => MirrorNetwork::mainnet(),
198                config::NetworkName::Testnet => MirrorNetwork::testnet(),
199                config::NetworkName::Previewnet => MirrorNetwork::previewnet(),
200            },
201        });
202
203        if let Some(operator) = operator {
204            client.0.operator.store(Some(Arc::new(operator)));
205        }
206
207        if let Some(mirror_network) = mirror_network {
208            client.set_mirror_network(mirror_network.load().addresses());
209        }
210
211        Ok(client)
212    }
213
214    /// Create a client from the given json config.
215    ///
216    /// # Errors
217    /// - [`Error::BasicParse`] if an error occurs parsing the configuration.
218    #[cfg(feature = "serde")]
219    pub fn from_config(json: &str) -> crate::Result<Self> {
220        let config = serde_json::from_str::<config::ClientConfigInner>(json)
221            .map_err(crate::Error::basic_parse)?
222            .into();
223
224        Self::from_config_data(config)
225    }
226
227    /// Returns the addresses for the configured mirror network.
228    ///
229    /// Unless _explicitly_ set, the return value isn't guaranteed to be anything in particular in order to allow future changes without breaking semver.
230    /// However, when a function such as `for_testnet` is used, _some_ valid value will be returned.
231    ///
232    /// Current return values (reminder that these are semver exempt)
233    ///
234    /// - mainnet: `["mainnet-public.mirrornode.hedera.com:443"]`
235    /// - testnet: `["testnet.mirrornode.hedera.com:443"]`
236    /// - previewnet: `["previewnet.mirrornode.hedera.com:443"]`
237    ///
238    /// # Examples
239    ///
240    /// ```
241    /// # #[tokio::main]
242    /// # async fn main() {
243    /// use hiero_sdk::Client;
244    ///
245    /// let client = Client::for_testnet();
246    ///
247    /// // note: This isn't *guaranteed* in a semver sense, but this is the current result.
248    /// let expected = Vec::from(["testnet.mirrornode.hedera.com:443".to_owned()]);
249    /// assert_eq!(expected, client.mirror_network());
250    ///
251    /// # }
252    /// ```
253    #[must_use]
254    pub fn mirror_network(&self) -> Vec<String> {
255        self.mirrornet().load().addresses().collect()
256    }
257
258    /// Sets the addresses to use for the mirror network.
259    ///
260    /// This is mostly useful if you used [`Self::for_network`] and need to set a mirror network.
261    pub fn set_mirror_network<I: IntoIterator<Item = String>>(&self, addresses: I) {
262        self.mirrornet().store(
263            MirrorNetworkData::from_addresses(addresses.into_iter().map(Cow::Owned).collect())
264                .into(),
265        );
266    }
267
268    /// Construct a client with the given nodes configured.
269    ///
270    /// Note that this disables network auto-updating.
271    ///
272    /// # Errors
273    /// - [`Error::BasicParse`] if an error occurs parsing the configuration.
274    // allowed for API compatibility.
275    #[allow(clippy::needless_pass_by_value)]
276    pub fn for_network(network: HashMap<String, AccountId>) -> crate::Result<Self> {
277        let network =
278            ManagedNetwork::new(Network::from_addresses(&network)?, MirrorNetwork::default());
279
280        Ok(ClientBuilder::new(network).disable_network_updating().build())
281    }
282
283    /// Construct a client from a select mirror network
284    pub async fn for_mirror_network(mirror_networks: Vec<String>) -> crate::Result<Self> {
285        Self::for_mirror_network_with_shard_realm(mirror_networks, 0, 0).await
286    }
287
288    /// Construct a client from a select mirror network with a specific shard and realm.
289    pub async fn for_mirror_network_with_shard_realm(
290        mirror_networks: Vec<String>,
291        shard: u64,
292        realm: u64,
293    ) -> crate::Result<Self> {
294        let network_addresses: HashMap<String, AccountId> = HashMap::new();
295        let network = ManagedNetwork::new(
296            Network::from_addresses(&network_addresses)?,
297            MirrorNetwork::from_addresses(mirror_networks.into_iter().map(Cow::Owned).collect()),
298        );
299
300        let client = ClientBuilder::new(network).build();
301        let address_book = if shard == 0 && realm == 0 {
302            NodeAddressBookQuery::default().execute(&client).await?
303        } else {
304            NodeAddressBookQuery::new().shard(shard).realm(realm).execute(&client).await?
305        };
306
307        client.set_network_from_address_book(address_book);
308
309        Ok(client)
310    }
311
312    /// Construct a Hiero client pre-configured for mainnet access.
313    #[must_use]
314    pub fn for_mainnet() -> Self {
315        ClientBuilder::new(ManagedNetwork::mainnet()).ledger_id(Some(LedgerId::mainnet())).build()
316    }
317
318    /// Construct a Hiero client pre-configured for testnet access.
319    #[must_use]
320    pub fn for_testnet() -> Self {
321        ClientBuilder::new(ManagedNetwork::testnet()).ledger_id(Some(LedgerId::testnet())).build()
322    }
323
324    /// Construct a Hiero client pre-configured for previewnet access.
325    #[must_use]
326    pub fn for_previewnet() -> Self {
327        ClientBuilder::new(ManagedNetwork::previewnet())
328            .ledger_id(Some(LedgerId::previewnet()))
329            .build()
330    }
331
332    /// Updates the network to use the given address book.
333    ///
334    /// Note: This is only really useful if you used `for_network`, because the network can auto-update.
335    ///
336    /// If network auto-updating is enabled this will eventually be overridden.
337    // allowed for API compatibility.
338    #[allow(clippy::needless_pass_by_value)]
339    pub fn set_network_from_address_book(&self, address_book: NodeAddressBook) {
340        self.net().update_from_address_book(&address_book);
341    }
342
343    /// Updates the network to use the given addresses.
344    ///
345    /// Note: This is only really useful if you used `for_network`, because the network can auto-update.
346    ///
347    /// If network auto-updating is enabled this will eventually be overridden.
348    ///
349    /// Tend to prefer [`set_network_from_address_book`](Self::set_network_from_address_book) where possible.
350    ///
351    /// # Errors
352    /// [`Error::BasicParse`](crate::Error::BasicParse) If any node address is unparsable.
353    // allowed for API compatibility.
354    #[allow(clippy::needless_pass_by_value)]
355    pub fn set_network(&self, network: HashMap<String, AccountId>) -> crate::Result<()> {
356        self.net().update_from_addresses(&network)?;
357
358        Ok(())
359    }
360
361    /// Returns the nodes associated with this client.
362    #[must_use]
363    pub fn network(&self) -> HashMap<String, AccountId> {
364        self.net().0.load().addresses()
365    }
366
367    /// Returns the max number of times a node can be retried before removing it from the network.
368    pub fn max_node_attempts(&self) -> Option<NonZeroUsize> {
369        self.net().0.load().max_node_attempts()
370    }
371
372    /// Set the max number of times a node can return a bad gRPC status before we remove it from the list.
373    pub fn set_max_node_attempts(&self, attempts: usize) {
374        self.net().0.load().set_max_node_attempts(NonZeroUsize::new(attempts))
375    }
376
377    /// Returns the max backoff interval for network nodes if gRPC response fail.    
378    pub fn max_node_backoff(&self) -> Duration {
379        self.net().0.load().max_backoff()
380    }
381
382    /// Sets max backoff interval for network nodes
383    pub fn set_max_node_backoff(&self, max_node_backoff: Duration) {
384        self.net().0.load().set_max_backoff(max_node_backoff)
385    }
386
387    /// Returns the initial backoff interval for network nodes if gRPC response fail.    
388    pub fn min_node_backoff(&self) -> Duration {
389        self.net().0.load().min_backoff()
390    }
391
392    /// Sets initial backoff interval for network nodes
393    pub fn set_min_node_backoff(&self, min_node_backoff: Duration) {
394        self.net().0.load().set_min_backoff(min_node_backoff)
395    }
396
397    /// Returns the max number of nodes to attempt for a request/transaction before failing.
398    pub fn max_nodes_per_request(&self) -> Option<u32> {
399        self.net().0.load().max_nodes_per_request()
400    }
401
402    /// Sets the max number of nodes to attempt for a request/transaction before failing.
403    ///
404    /// If set to `None`, then the client will attempt to use all healthy nodes.
405    pub fn set_max_nodes_per_request(&self, max_nodes: Option<u32>) {
406        self.net().0.load().set_max_nodes_per_request(max_nodes)
407    }
408
409    /// Construct a hedera client pre-configured for access to the given network.
410    ///
411    /// Currently supported network names are `"mainnet"`, `"testnet"`, and `"previewnet"`.
412    ///
413    /// # Errors
414    /// - [`Error::BasicParse`] if the network name is not a supported network name.
415    pub fn for_name(name: &str) -> crate::Result<Self> {
416        match name {
417            "mainnet" => Ok(Self::for_mainnet()),
418            "testnet" => Ok(Self::for_testnet()),
419            "previewnet" => Ok(Self::for_previewnet()),
420            "localhost" => {
421                let mut network: HashMap<String, AccountId> = HashMap::new();
422                network.insert("127.0.0.1:50211".to_string(), AccountId::new(0, 0, 3));
423
424                let client = Client::for_network(network).unwrap();
425                client.set_mirror_network(["127.0.0.1:5600".to_string()]);
426                Ok(client)
427            }
428            _ => Err(Error::basic_parse(format!("Unknown network name {name}"))),
429        }
430    }
431
432    // optimized function to avoid allocations/pointer chasing.
433    // this shouldn't be exposed because it exposes repr.
434    pub(crate) fn ledger_id_internal(&self) -> arc_swap::Guard<Option<Arc<LedgerId>>> {
435        self.0.ledger_id.load()
436    }
437
438    /// Sets the ledger ID for the Client's network.
439    pub fn set_ledger_id(&self, ledger_id: Option<LedgerId>) {
440        self.0.ledger_id.store(ledger_id.map(Arc::new));
441    }
442
443    /// Returns true if checksums should be automatically validated.
444    #[must_use]
445    pub fn auto_validate_checksums(&self) -> bool {
446        self.0.auto_validate_checksums.load(Ordering::Relaxed)
447    }
448
449    /// Enable or disable automatic entity ID checksum validation.
450    pub fn set_auto_validate_checksums(&self, value: bool) {
451        self.0.auto_validate_checksums.store(value, Ordering::Relaxed);
452    }
453
454    /// Returns true if transaction IDs should be automatically regenerated.
455    ///
456    /// This is `true` by default.
457    #[must_use]
458    pub fn default_regenerate_transaction_id(&self) -> bool {
459        self.0.regenerate_transaction_ids.load(Ordering::Relaxed)
460    }
461
462    /// Enable or disable transaction ID regeneration.
463    pub fn set_default_regenerate_transaction_id(&self, value: bool) {
464        self.0.regenerate_transaction_ids.store(value, Ordering::Relaxed);
465    }
466
467    /// Returns whether receipt/record query failover is enabled.
468    ///
469    /// When enabled, receipt and record queries will fail over to other nodes
470    /// if the submitting node is unavailable, improving availability at the cost
471    /// of strict correctness guarantees.
472    ///
473    /// Default: `false` (queries pinned to submitting node only)
474    #[must_use]
475    pub fn get_enable_receipt_record_query_failover(&self) -> bool {
476        self.0.enable_receipt_record_query_failover.load(Ordering::Relaxed)
477    }
478
479    /// Enable or disable receipt/record query failover.
480    ///
481    /// When enabled, `get_receipt()` and `get_record()` queries will fail over to other nodes
482    /// if the submitting node is unavailable or times out. This improves availability under
483    /// high concurrency but trades strict correctness guarantees.
484    ///
485    /// When disabled (default), queries are strictly pinned to the submitting node.
486    ///
487    /// # Arguments
488    /// * `enable` - `true` to enable failover, `false` to disable (default)
489    ///
490    /// # Example
491    /// ```no_run
492    /// # use hiero_sdk::Client;
493    /// let client = Client::for_testnet();
494    /// // Enable failover for better availability
495    /// client.set_enable_receipt_record_query_failover(true);
496    /// ```
497    pub fn set_enable_receipt_record_query_failover(&self, enable: bool) {
498        self.0.enable_receipt_record_query_failover.store(enable, Ordering::Relaxed);
499    }
500
501    /// Sets the account that will, by default, be paying for transactions and queries built with
502    /// this client.
503    ///
504    /// The operator account ID is used to generate the default transaction ID for all transactions
505    /// executed with this client.
506    ///
507    /// The operator private key is used to sign all transactions executed by this client.
508    pub fn set_operator(&self, id: AccountId, key: PrivateKey) {
509        self.0
510            .operator
511            .store(Some(Arc::new(Operator { account_id: id, signer: AnySigner::PrivateKey(key) })));
512    }
513
514    /// Sets the account that will, by default, be paying for transactions and queries built with
515    /// this client.
516    ///
517    /// The operator account ID is used to generate the default transaction ID for all transactions
518    /// executed with this client.
519    ///
520    /// The operator signer is used to sign all transactions executed by this client.
521    pub fn set_operator_with<F: Fn(&[u8]) -> Vec<u8> + Send + Sync + 'static>(
522        &self,
523        id: AccountId,
524        public_key: PublicKey,
525        f: F,
526    ) {
527        self.0.operator.store(Some(Arc::new(Operator {
528            account_id: id,
529            signer: AnySigner::arbitrary(Box::new(public_key), f),
530        })));
531    }
532
533    /// Gets a reference to the configured network.
534    pub(crate) fn net(&self) -> &Network {
535        &self.0.network.primary
536    }
537
538    /// Gets a reference to the configured mirror network.
539    pub(crate) fn mirrornet(&self) -> &MirrorNetwork {
540        &self.0.network.mirror
541    }
542
543    /// Sets the maximum transaction fee to be used when no explicit max transaction fee is set.
544    ///
545    /// Note: Setting `amount` to zero is "unlimited"
546    /// # Panics
547    /// - if amount is negative
548    pub fn set_default_max_transaction_fee(&self, amount: Hbar) {
549        assert!(amount >= Hbar::ZERO);
550        self.0.max_transaction_fee_tinybar.store(amount.to_tinybars() as u64, Ordering::Relaxed);
551    }
552
553    /// Gets the maximum transaction fee the paying account is willing to pay.
554    #[must_use]
555    pub fn default_max_transaction_fee(&self) -> Option<Hbar> {
556        let val = self.0.max_transaction_fee_tinybar.load(Ordering::Relaxed);
557
558        (val > 0).then(|| Hbar::from_tinybars(val as i64))
559    }
560
561    /// Gets the maximum query fee the paying account is willing to pay.
562    #[must_use]
563    pub fn default_max_query_payment(&self) -> Option<Hbar> {
564        let val = self.0.max_query_payment_tinybar.load(Ordering::Relaxed);
565
566        (val > 0).then(|| Hbar::from_tinybars(val as i64))
567    }
568
569    /// Sets the maximum query payment to be used when no explicit max query payment is set.
570    ///
571    /// Note: Setting `amount` to zero is "unlimited"
572    /// # Panics
573    /// - if amount is negative
574    pub fn set_default_max_query_payment(&self, amount: Hbar) {
575        assert!(amount >= Hbar::ZERO);
576        self.0.max_query_payment_tinybar.store(amount.to_tinybars() as u64, Ordering::Relaxed);
577    }
578
579    /// Returns the maximum amount of time that will be spent on a request.
580    #[must_use]
581    pub fn request_timeout(&self) -> Option<Duration> {
582        self.backoff().request_timeout
583    }
584
585    /// Sets the maximum amount of time that will be spent on a request.
586    pub fn set_request_timeout(&self, timeout: Option<Duration>) {
587        self.0.backoff.write().request_timeout = timeout;
588    }
589
590    /// Returns the maximum number of attempts for a request.
591    #[must_use]
592    pub fn max_attempts(&self) -> usize {
593        self.backoff().max_attempts
594    }
595
596    /// Sets the maximum number of attempts for a request.
597    pub fn set_max_attempts(&self, max_attempts: usize) {
598        self.0.backoff.write().max_attempts = max_attempts;
599    }
600
601    /// The initial backoff for a request being executed.
602    #[doc(alias = "initial_backoff")]
603    #[must_use]
604    pub fn min_backoff(&self) -> Duration {
605        self.backoff().initial_backoff
606    }
607
608    /// Sets the initial backoff for a request being executed.
609    #[doc(alias = "set_initial_backoff")]
610    pub fn set_min_backoff(&self, max_backoff: Duration) {
611        self.0.backoff.write().max_backoff = max_backoff;
612    }
613
614    /// Returns the maximum amount of time a request will wait between attempts.
615    #[must_use]
616    pub fn max_backoff(&self) -> Duration {
617        self.backoff().max_backoff
618    }
619
620    /// Sets the maximum amount of time a request will wait between attempts.
621    pub fn set_max_backoff(&self, max_backoff: Duration) {
622        self.0.backoff.write().max_backoff = max_backoff;
623    }
624
625    /// Returns the gRPC deadline for individual requests.
626    #[must_use]
627    pub fn grpc_deadline(&self) -> Duration {
628        self.backoff().grpc_deadline
629    }
630
631    /// Sets the gRPC deadline for individual requests.
632    ///
633    /// This timeout is used both for establishing connections to nodes and for individual gRPC calls.
634    pub fn set_grpc_deadline(&self, grpc_deadline: Duration) {
635        self.0.backoff.write().grpc_deadline = grpc_deadline;
636    }
637
638    #[must_use]
639    pub(crate) fn backoff(&self) -> ClientBackoff {
640        *self.0.backoff.read()
641    }
642
643    // keep this internal (repr)
644    pub(crate) fn load_operator(&self) -> arc_swap::Guard<Option<Arc<Operator>>> {
645        self.0.operator.load()
646    }
647
648    // keep this internal (repr)
649    pub(crate) fn full_load_operator(&self) -> Option<Arc<Operator>> {
650        self.0.operator.load_full()
651    }
652
653    /// Send a ping to the given node.
654    pub async fn ping(&self, node_account_id: AccountId) -> crate::Result<()> {
655        PingQuery::new(node_account_id).execute(self, None).await
656    }
657
658    /// Send a ping to the given node, canceling the ping after `timeout` has elapsed.
659    pub async fn ping_with_timeout(
660        &self,
661        node_account_id: AccountId,
662        timeout: Duration,
663    ) -> crate::Result<()> {
664        PingQuery::new(node_account_id).execute(self, Some(timeout)).await
665    }
666
667    /// Send a ping to all nodes.
668    pub async fn ping_all(&self) -> crate::Result<()> {
669        futures_util::future::try_join_all(
670            self.net().0.load().node_ids().iter().map(|it| self.ping(*it)),
671        )
672        .await?;
673
674        Ok(())
675    }
676
677    /// Send a ping to all nodes, canceling the ping after `timeout` has elapsed.
678    pub async fn ping_all_with_timeout(&self, timeout: Duration) -> crate::Result<()> {
679        futures_util::future::try_join_all(
680            self.net().0.load().node_ids().iter().map(|it| self.ping_with_timeout(*it, timeout)),
681        )
682        .await?;
683
684        Ok(())
685    }
686
687    /// Returns the frequency at which the network will update (if it will update at all).
688    #[must_use = "this function has no side-effects"]
689    pub fn network_update_period(&self) -> Option<Duration> {
690        *self.0.network_update_tx.borrow()
691    }
692
693    /// Sets the frequency at which the network will update.
694    ///
695    /// Note that network updates will not affect any in-flight requests.
696    pub fn set_network_update_period(&self, period: Option<Duration>) {
697        self.0.network_update_tx.send_if_modified(|place| {
698            let changed = *place == period;
699            if changed {
700                *place = period;
701            }
702
703            changed
704        });
705    }
706
707    /// Triggers an immediate network update from the address book.
708    /// Note: This method is not part of the public API and may be changed or removed in future versions.
709    pub(crate) async fn refresh_network(&self) {
710        match NodeAddressBookQuery::new()
711            .execute_mirrornet(self.mirrornet().load().channel(self.grpc_deadline()), None)
712            .await
713        {
714            Ok(address_book) => {
715                log::info!("Successfully updated network address book");
716                self.set_network_from_address_book(address_book);
717            }
718            Err(e) => {
719                log::warn!("Failed to update network address book: {e:?}");
720            }
721        }
722    }
723
724    /// Returns the Account ID for the operator.
725    #[must_use]
726    pub fn get_operator_account_id(&self) -> Option<AccountId> {
727        self.load_operator().as_deref().map(|it| it.account_id)
728    }
729
730    /// Returns the `PublicKey` for the current operator.
731    #[must_use]
732    pub fn get_operator_public_key(&self) -> Option<PublicKey> {
733        self.load_operator().as_deref().map(|it| it.signer.public_key())
734    }
735}