Skip to main content

nym_validator_client/
client.rs

1// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::nyxd::{self, NyxdClient};
5use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
6use crate::signing::signer::{NoSigner, OfflineSigner};
7use crate::{
8    DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient, ValidatorClientError,
9};
10use nym_api_requests::ecash::models::{
11    AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
12    BatchRedeemTicketsBody, EcashBatchTicketRedemptionResponse, EcashTicketVerificationResponse,
13    IssuedTicketbooksChallengeCommitmentRequest, IssuedTicketbooksChallengeCommitmentResponse,
14    IssuedTicketbooksDataRequest, IssuedTicketbooksDataResponse, IssuedTicketbooksForCountResponse,
15    IssuedTicketbooksForResponse, VerifyEcashTicketBody,
16};
17use nym_api_requests::ecash::{
18    BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
19    PartialExpirationDateSignatureResponse, VerificationKeyResponse,
20};
21use nym_api_requests::models::{
22    ApiHealthResponse, GatewayCoreStatusResponse, HistoricalPerformanceResponse,
23    MixnodeCoreStatusResponse, NymNodeDescriptionV1, NymNodeDescriptionV2,
24};
25use nym_api_requests::nym_nodes::{
26    NodesByAddressesResponse, SemiSkimmedNodesWithMetadata, SkimmedNodeV1, SkimmedNodesWithMetadata,
27};
28use nym_coconut_dkg_common::types::EpochId;
29use nym_http_api_client::UserAgent;
30use nym_mixnet_contract_common::EpochRewardedSet;
31use nym_network_defaults::NymNetworkDetails;
32use std::net::IpAddr;
33use time::Date;
34use url::Url;
35
36pub use crate::nym_api::NymApiClientExt;
37pub use nym_mixnet_contract_common::{
38    mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, NodeId, NymNodeDetails,
39};
40// re-export the type to not break existing imports
41pub use crate::coconut::EcashApiClient;
42
43#[cfg(feature = "http-client")]
44use crate::rpc::http_client;
45#[cfg(feature = "http-client")]
46use crate::{DirectSigningHttpRpcValidatorClient, HttpRpcClient, QueryHttpRpcValidatorClient};
47
48// a simple helper macro to define to repeatedly call a paged query until a full response is constructed
49macro_rules! collect_paged_skimmed_v2 {
50    ( $self:ident, $f: ident ) => {{
51        // unroll first loop iteration in order to obtain the metadata
52        let mut page = 0;
53        let res = $self
54            .nym_api
55            .$f(false, Some(page), None, $self.use_bincode)
56            .await?;
57        let mut nodes = res.nodes.data;
58        let metadata = res.metadata;
59
60        if res.nodes.pagination.total == nodes.len() {
61            return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
62        }
63
64        page += 1;
65
66        loop {
67            let mut res = $self
68                .nym_api
69                .$f(false, Some(page), None, $self.use_bincode)
70                .await?;
71
72            if !metadata.consistency_check(&res.metadata) {
73                return Err(ValidatorClientError::InconsistentPagedMetadata);
74            }
75
76            nodes.append(&mut res.nodes.data);
77            if nodes.len() < res.nodes.pagination.total {
78                page += 1
79            } else {
80                break;
81            }
82        }
83
84        Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
85    }};
86}
87
88#[must_use]
89#[derive(Debug, Clone)]
90pub struct Config {
91    api_url: Url,
92    nyxd_url: Url,
93
94    // TODO: until refactored, this is a dead field under some features
95    nyxd_config: nyxd::Config,
96}
97
98impl TryFrom<NymNetworkDetails> for Config {
99    type Error = ValidatorClientError;
100
101    fn try_from(value: NymNetworkDetails) -> Result<Self, Self::Error> {
102        Config::try_from_nym_network_details(&value)
103    }
104}
105
106impl Config {
107    pub fn new(nyxd_url: Url, api_url: Url, nyxd_config: nyxd::Config) -> Self {
108        Config {
109            api_url,
110            nyxd_url,
111            nyxd_config,
112        }
113    }
114
115    pub fn try_from_nym_network_details(
116        details: &NymNetworkDetails,
117    ) -> Result<Self, ValidatorClientError> {
118        let mut api_url = details
119            .endpoints
120            .iter()
121            .filter_map(|d| d.api_url.as_ref())
122            .map(|url| Url::parse(url))
123            .collect::<Result<Vec<_>, _>>()?;
124
125        if let Some(nym_api_urls) = details.nym_api_urls.as_ref() {
126            api_url.extend(
127                nym_api_urls
128                    .iter()
129                    .map(|url| url.url.parse())
130                    .collect::<Result<Vec<_>, _>>()?,
131            );
132        }
133
134        if api_url.is_empty() {
135            return Err(ValidatorClientError::NoAPIUrlAvailable);
136        }
137
138        Ok(Config {
139            api_url: api_url.pop().unwrap(),
140            nyxd_url: details.endpoints[0]
141                .nyxd_url
142                .parse()
143                .map_err(ValidatorClientError::MalformedUrlProvided)?,
144            nyxd_config: nyxd::Config::try_from_nym_network_details(details)?,
145        })
146    }
147
148    // TODO: this method shouldn't really exist as all information should be included immediately
149    // via `from_nym_network_details`, but it's here for, you guessed it, legacy compatibility
150    pub fn with_urls(mut self, nyxd_url: Url, api_url: Url) -> Self {
151        self.nyxd_url = nyxd_url;
152        self.api_url = api_url;
153        self
154    }
155
156    pub fn with_nyxd_url(mut self, nyxd_url: Url) -> Self {
157        self.nyxd_url = nyxd_url;
158        self
159    }
160
161    pub fn with_simulated_gas_multiplier(mut self, gas_multiplier: f32) -> Self {
162        self.nyxd_config.simulated_gas_multiplier = gas_multiplier;
163        self
164    }
165}
166
167pub struct Client<C, S = NoSigner> {
168    // ideally they would have been read-only, but unfortunately rust doesn't have such features
169    // #[deprecated(note = "please use `nym_api_client` instead")]
170    pub nym_api: nym_http_api_client::Client,
171    // pub nym_api_client: NymApiClient,
172    pub nyxd: NyxdClient<C, S>,
173}
174
175#[cfg(feature = "http-client")]
176impl Client<HttpRpcClient, DirectSecp256k1HdWallet> {
177    pub fn new_signing(
178        config: Config,
179        mnemonic: bip39::Mnemonic,
180    ) -> Result<DirectSigningHttpRpcValidatorClient, ValidatorClientError> {
181        let rpc_client = http_client(config.nyxd_url.as_str())?;
182        let prefix = &config.nyxd_config.chain_details.bech32_account_prefix;
183        let wallet = DirectSecp256k1HdWallet::checked_from_mnemonic(prefix, mnemonic)?;
184
185        Ok(Self::new_signing_with_rpc_client(
186            config, rpc_client, wallet,
187        ))
188    }
189
190    pub fn change_nyxd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> {
191        self.nyxd.change_endpoint(new_endpoint.as_ref())?;
192        Ok(())
193    }
194}
195
196#[allow(deprecated)]
197impl Client<crate::ReqwestRpcClient, DirectSecp256k1HdWallet> {
198    pub fn new_reqwest_signing(
199        config: Config,
200        mnemonic: bip39::Mnemonic,
201    ) -> DirectSigningReqwestRpcValidatorClient {
202        let rpc_client = crate::ReqwestRpcClient::new(config.nyxd_url.clone());
203        let prefix = &config.nyxd_config.chain_details.bech32_account_prefix;
204        let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
205
206        Self::new_signing_with_rpc_client(config, rpc_client, wallet)
207    }
208}
209
210#[cfg(feature = "http-client")]
211impl Client<HttpRpcClient> {
212    pub fn new_query(config: Config) -> Result<QueryHttpRpcValidatorClient, ValidatorClientError> {
213        let rpc_client = http_client(config.nyxd_url.as_str())?;
214        Ok(Self::new_with_rpc_client(config, rpc_client))
215    }
216
217    pub fn change_nyxd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> {
218        self.nyxd = NyxdClient::connect(self.nyxd.current_config().clone(), new_endpoint.as_ref())?;
219        Ok(())
220    }
221}
222
223#[allow(deprecated)]
224impl Client<crate::ReqwestRpcClient> {
225    pub fn new_reqwest_query(config: Config) -> QueryReqwestRpcValidatorClient {
226        let rpc_client = crate::ReqwestRpcClient::new(config.nyxd_url.clone());
227        Self::new_with_rpc_client(config, rpc_client)
228    }
229}
230
231impl<C> Client<C> {
232    pub fn new_with_rpc_client(config: Config, rpc_client: C) -> Self {
233        let nym_api_client = nym_http_api_client::Client::new(config.api_url.clone(), None);
234
235        Client {
236            nym_api: nym_api_client,
237            nyxd: NyxdClient::new(config.nyxd_config, rpc_client),
238        }
239    }
240}
241
242impl<C, S> Client<C, S> {
243    pub fn new_signing_with_rpc_client(config: Config, rpc_client: C, signer: S) -> Self
244    where
245        S: OfflineSigner,
246    {
247        let nym_api_client = nym_http_api_client::Client::new(config.api_url.clone(), None);
248
249        Client {
250            nym_api: nym_api_client,
251            nyxd: NyxdClient::new_signing(config.nyxd_config, rpc_client, signer),
252        }
253    }
254}
255
256// validator-api wrappers
257// we have to allow the use of deprecated method here as they're calling the deprecated trait methods
258#[allow(deprecated)]
259impl<C, S> Client<C, S> {
260    pub fn api_url(&self) -> &Url {
261        self.nym_api.current_url().as_ref()
262    }
263
264    pub fn change_nym_api(&mut self, new_endpoint: Url) {
265        self.nym_api.change_base_urls(vec![new_endpoint.into()])
266    }
267
268    pub async fn get_full_node_performance_history(
269        &self,
270        node_id: NodeId,
271    ) -> Result<Vec<HistoricalPerformanceResponse>, ValidatorClientError> {
272        // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
273        let mut page = 0;
274        let mut history = Vec::new();
275
276        loop {
277            let mut res = self
278                .nym_api
279                .get_node_performance_history(node_id, Some(page), None)
280                .await?;
281
282            history.append(&mut res.history.data);
283            if history.len() < res.history.pagination.total {
284                page += 1
285            } else {
286                break;
287            }
288        }
289
290        Ok(history)
291    }
292
293    #[deprecated(note = "use get_all_cached_described_nodes_v2 instead")]
294    pub async fn get_all_cached_described_nodes(
295        &self,
296    ) -> Result<Vec<NymNodeDescriptionV1>, ValidatorClientError> {
297        Ok(self.nym_api.get_all_described_nodes().await?)
298    }
299
300    pub async fn get_all_cached_described_nodes_v2(
301        &self,
302    ) -> Result<Vec<NymNodeDescriptionV2>, ValidatorClientError> {
303        Ok(self.nym_api.get_all_described_nodes_v2().await?)
304    }
305
306    pub async fn get_all_cached_bonded_nym_nodes(
307        &self,
308    ) -> Result<Vec<NymNodeDetails>, ValidatorClientError> {
309        self.nym_api.get_all_bonded_nym_nodes().await
310    }
311
312    pub async fn blind_sign(
313        &self,
314        request_body: &BlindSignRequestBody,
315    ) -> Result<BlindedSignatureResponse, ValidatorClientError> {
316        Ok(self.nym_api.blind_sign(request_body).await?)
317    }
318}
319
320/// DEPRECATED: Use nym_http_api_client::Client with from_network() or with_bincode() instead
321#[deprecated(
322    since = "1.2.0",
323    note = "Use nym_http_api_client::Client::from_network() or ClientBuilder::with_bincode() instead"
324)]
325#[derive(Clone)]
326pub struct NymApiClient {
327    pub use_bincode: bool,
328    pub nym_api: nym_http_api_client::Client,
329    // TODO: perhaps if we really need it at some (currently I don't see any reasons for it)
330    // we could re-implement the communication with the REST API on port 1317
331}
332
333// we have to allow the use of deprecated method here as they're calling the deprecated trait methods
334#[allow(deprecated)]
335impl NymApiClient {
336    #[cfg(not(target_arch = "wasm32"))]
337    pub fn new_with_timeout(api_url: Url, timeout: std::time::Duration) -> Self {
338        let nym_api = nym_http_api_client::Client::new(api_url, Some(timeout));
339
340        NymApiClient {
341            use_bincode: true,
342            nym_api,
343        }
344    }
345
346    #[must_use]
347    pub fn with_bincode(mut self, use_bincode: bool) -> Self {
348        self.use_bincode = use_bincode;
349        self
350    }
351
352    pub fn new_with_user_agent(api_url: Url, user_agent: impl Into<UserAgent>) -> Self {
353        let nym_api = nym_http_api_client::Client::builder(api_url)
354            .expect("invalid api url")
355            .with_user_agent(user_agent.into())
356            .build()
357            .expect("failed to build nym api client");
358
359        NymApiClient {
360            use_bincode: false,
361            nym_api,
362        }
363    }
364
365    pub fn api_url(&self) -> &Url {
366        self.nym_api.current_url().as_ref()
367    }
368
369    pub fn change_nym_api(&mut self, new_endpoint: Url) {
370        self.nym_api.change_base_urls(vec![new_endpoint.into()]);
371    }
372
373    #[deprecated(note = "use get_all_basic_active_mixing_assigned_nodes instead")]
374    pub async fn get_basic_mixnodes(&self) -> Result<Vec<SkimmedNodeV1>, ValidatorClientError> {
375        Ok(self.nym_api.get_basic_mixnodes().await?.nodes)
376    }
377
378    #[deprecated(note = "use get_all_basic_entry_assigned_nodes instead")]
379    pub async fn get_basic_gateways(&self) -> Result<Vec<SkimmedNodeV1>, ValidatorClientError> {
380        Ok(self.nym_api.get_basic_gateways().await?.nodes)
381    }
382
383    pub async fn get_current_rewarded_set(&self) -> Result<EpochRewardedSet, ValidatorClientError> {
384        Ok(self.nym_api.get_rewarded_set().await?.into())
385    }
386
387    /// retrieve basic information for nodes are capable of operating as an entry gateway
388    /// this includes legacy gateways and nym-nodes
389    #[deprecated(note = "use get_all_basic_entry_assigned_nodes_with_metadata instead")]
390    pub async fn get_all_basic_entry_assigned_nodes(
391        &self,
392    ) -> Result<Vec<SkimmedNodeV1>, ValidatorClientError> {
393        self.get_all_basic_entry_assigned_nodes_with_metadata()
394            .await
395            .map(|res| res.nodes)
396    }
397
398    pub async fn get_all_basic_entry_assigned_nodes_with_metadata(
399        &self,
400    ) -> Result<SkimmedNodesWithMetadata, ValidatorClientError> {
401        collect_paged_skimmed_v2!(self, get_basic_entry_assigned_nodes_v2)
402    }
403
404    /// retrieve basic information for nodes that got assigned 'mixing' node in this epoch
405    /// this includes legacy mixnodes and nym-nodes
406    #[deprecated(note = "use get_all_basic_active_mixing_assigned_nodes_with_metadata instead")]
407    pub async fn get_all_basic_active_mixing_assigned_nodes(
408        &self,
409    ) -> Result<Vec<SkimmedNodeV1>, ValidatorClientError> {
410        self.get_all_basic_active_mixing_assigned_nodes_with_metadata()
411            .await
412            .map(|res| res.nodes)
413    }
414
415    pub async fn get_all_basic_active_mixing_assigned_nodes_with_metadata(
416        &self,
417    ) -> Result<SkimmedNodesWithMetadata, ValidatorClientError> {
418        collect_paged_skimmed_v2!(self, get_basic_active_mixing_assigned_nodes_v2)
419    }
420
421    /// retrieve basic information for nodes are capable of operating as a mixnode
422    /// this includes legacy mixnodes and nym-nodes
423    #[deprecated(note = "use get_all_basic_mixing_capable_nodes_with_metadata instead")]
424    pub async fn get_all_basic_mixing_capable_nodes(
425        &self,
426    ) -> Result<Vec<SkimmedNodeV1>, ValidatorClientError> {
427        self.get_all_basic_mixing_capable_nodes_with_metadata()
428            .await
429            .map(|res| res.nodes)
430    }
431
432    pub async fn get_all_basic_mixing_capable_nodes_with_metadata(
433        &self,
434    ) -> Result<SkimmedNodesWithMetadata, ValidatorClientError> {
435        collect_paged_skimmed_v2!(self, get_basic_mixing_capable_nodes_v2)
436    }
437
438    /// retrieve basic information for all bonded nodes on the network
439    #[deprecated(note = "use get_all_basic_nodes_with_metadata instead")]
440    pub async fn get_all_basic_nodes(&self) -> Result<Vec<SkimmedNodeV1>, ValidatorClientError> {
441        self.get_all_basic_nodes_with_metadata()
442            .await
443            .map(|res| res.nodes)
444    }
445
446    pub async fn get_all_basic_nodes_with_metadata(
447        &self,
448    ) -> Result<SkimmedNodesWithMetadata, ValidatorClientError> {
449        collect_paged_skimmed_v2!(self, get_basic_nodes_v2)
450    }
451
452    /// retrieve expanded information for all bonded nodes on the network
453    pub async fn get_all_expanded_nodes(
454        &self,
455    ) -> Result<SemiSkimmedNodesWithMetadata, ValidatorClientError> {
456        // Unroll the first iteration to get the metadata
457        let mut page = 0;
458
459        let res = self
460            .nym_api
461            .get_expanded_nodes(false, Some(page), None)
462            .await?;
463        let mut nodes = res.nodes.data;
464        let metadata = res.metadata;
465
466        if res.nodes.pagination.total == nodes.len() {
467            return Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata));
468        }
469
470        page += 1;
471
472        loop {
473            let mut res = self
474                .nym_api
475                .get_expanded_nodes(false, Some(page), None)
476                .await?;
477
478            nodes.append(&mut res.nodes.data);
479            if nodes.len() < res.nodes.pagination.total {
480                page += 1
481            } else {
482                break;
483            }
484        }
485
486        Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata))
487    }
488
489    pub async fn health(&self) -> Result<ApiHealthResponse, ValidatorClientError> {
490        Ok(self.nym_api.health().await?)
491    }
492
493    #[deprecated(note = "use .get_all_described_nodes_v2 instead")]
494    pub async fn get_all_described_nodes(
495        &self,
496    ) -> Result<Vec<NymNodeDescriptionV1>, ValidatorClientError> {
497        // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
498        let mut page = 0;
499        let mut descriptions = Vec::new();
500
501        loop {
502            let mut res = self.nym_api.get_nodes_described(Some(page), None).await?;
503
504            descriptions.append(&mut res.data);
505            if descriptions.len() < res.pagination.total {
506                page += 1
507            } else {
508                break;
509            }
510        }
511
512        Ok(descriptions)
513    }
514
515    pub async fn get_all_described_nodes_v2(
516        &self,
517    ) -> Result<Vec<NymNodeDescriptionV2>, ValidatorClientError> {
518        // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
519        let mut page = 0;
520        let mut descriptions = Vec::new();
521
522        loop {
523            let mut res = self
524                .nym_api
525                .get_nodes_described_v2(Some(page), None)
526                .await?;
527
528            descriptions.append(&mut res.data);
529            if descriptions.len() < res.pagination.total {
530                page += 1
531            } else {
532                break;
533            }
534        }
535
536        Ok(descriptions)
537    }
538
539    pub async fn get_all_bonded_nym_nodes(
540        &self,
541    ) -> Result<Vec<NymNodeDetails>, ValidatorClientError> {
542        // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
543        let mut page = 0;
544        let mut bonds = Vec::new();
545
546        loop {
547            let mut res = self.nym_api.get_nym_nodes(Some(page), None).await?;
548
549            bonds.append(&mut res.data);
550            if bonds.len() < res.pagination.total {
551                page += 1
552            } else {
553                break;
554            }
555        }
556
557        Ok(bonds)
558    }
559
560    #[deprecated]
561    pub async fn get_gateway_core_status_count(
562        &self,
563        identity: IdentityKeyRef<'_>,
564        since: Option<i64>,
565    ) -> Result<GatewayCoreStatusResponse, ValidatorClientError> {
566        Ok(self
567            .nym_api
568            .get_gateway_core_status_count(identity, since)
569            .await?)
570    }
571
572    #[deprecated]
573    pub async fn get_mixnode_core_status_count(
574        &self,
575        mix_id: NodeId,
576        since: Option<i64>,
577    ) -> Result<MixnodeCoreStatusResponse, ValidatorClientError> {
578        Ok(self
579            .nym_api
580            .get_mixnode_core_status_count(mix_id, since)
581            .await?)
582    }
583
584    pub async fn blind_sign(
585        &self,
586        request_body: &BlindSignRequestBody,
587    ) -> Result<BlindedSignatureResponse, ValidatorClientError> {
588        Ok(self.nym_api.blind_sign(request_body).await?)
589    }
590
591    pub async fn verify_ecash_ticket(
592        &self,
593        request_body: &VerifyEcashTicketBody,
594    ) -> Result<EcashTicketVerificationResponse, ValidatorClientError> {
595        Ok(self.nym_api.verify_ecash_ticket(request_body).await?)
596    }
597
598    pub async fn batch_redeem_ecash_tickets(
599        &self,
600        request_body: &BatchRedeemTicketsBody,
601    ) -> Result<EcashBatchTicketRedemptionResponse, ValidatorClientError> {
602        Ok(self
603            .nym_api
604            .batch_redeem_ecash_tickets(request_body)
605            .await?)
606    }
607
608    pub async fn partial_expiration_date_signatures(
609        &self,
610        expiration_date: Option<Date>,
611        epoch_id: Option<EpochId>,
612    ) -> Result<PartialExpirationDateSignatureResponse, ValidatorClientError> {
613        Ok(self
614            .nym_api
615            .partial_expiration_date_signatures(expiration_date, epoch_id)
616            .await?)
617    }
618
619    pub async fn partial_coin_indices_signatures(
620        &self,
621        epoch_id: Option<EpochId>,
622    ) -> Result<PartialCoinIndicesSignatureResponse, ValidatorClientError> {
623        Ok(self
624            .nym_api
625            .partial_coin_indices_signatures(epoch_id)
626            .await?)
627    }
628
629    pub async fn global_expiration_date_signatures(
630        &self,
631        expiration_date: Option<Date>,
632        epoch_id: Option<EpochId>,
633    ) -> Result<AggregatedExpirationDateSignatureResponse, ValidatorClientError> {
634        Ok(self
635            .nym_api
636            .global_expiration_date_signatures(expiration_date, epoch_id)
637            .await?)
638    }
639
640    pub async fn global_coin_indices_signatures(
641        &self,
642        epoch_id: Option<EpochId>,
643    ) -> Result<AggregatedCoinIndicesSignatureResponse, ValidatorClientError> {
644        Ok(self
645            .nym_api
646            .global_coin_indices_signatures(epoch_id)
647            .await?)
648    }
649
650    pub async fn master_verification_key(
651        &self,
652        epoch_id: Option<EpochId>,
653    ) -> Result<VerificationKeyResponse, ValidatorClientError> {
654        Ok(self.nym_api.master_verification_key(epoch_id).await?)
655    }
656
657    pub async fn issued_ticketbooks_for(
658        &self,
659        expiration_date: Date,
660    ) -> Result<IssuedTicketbooksForResponse, ValidatorClientError> {
661        Ok(self.nym_api.issued_ticketbooks_for(expiration_date).await?)
662    }
663
664    pub async fn issued_ticketbooks_for_count(
665        &self,
666        expiration_date: Date,
667    ) -> Result<IssuedTicketbooksForCountResponse, ValidatorClientError> {
668        Ok(self
669            .nym_api
670            .issued_ticketbooks_for_count(expiration_date)
671            .await?)
672    }
673
674    pub async fn issued_ticketbooks_challenge_commitment(
675        &self,
676        request: &IssuedTicketbooksChallengeCommitmentRequest,
677    ) -> Result<IssuedTicketbooksChallengeCommitmentResponse, ValidatorClientError> {
678        Ok(self
679            .nym_api
680            .issued_ticketbooks_challenge_commitment(request)
681            .await?)
682    }
683
684    pub async fn issued_ticketbooks_data(
685        &self,
686        request: &IssuedTicketbooksDataRequest,
687    ) -> Result<IssuedTicketbooksDataResponse, ValidatorClientError> {
688        Ok(self.nym_api.issued_ticketbooks_data(request).await?)
689    }
690
691    pub async fn nodes_by_addresses(
692        &self,
693        addresses: Vec<IpAddr>,
694    ) -> Result<NodesByAddressesResponse, ValidatorClientError> {
695        Ok(self.nym_api.nodes_by_addresses(addresses).await?)
696    }
697}