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