Skip to main content

nym_validator_client/coconut/
mod.rs

1// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::nyxd::contract_traits::{DkgQueryClient, PagedDkgQueryClient};
5use crate::nyxd::error::NyxdError;
6use nym_coconut_dkg_common::types::{EpochId, NodeIndex};
7use nym_coconut_dkg_common::verification_key::ContractVKShare;
8use nym_compact_ecash::error::CompactEcashError;
9use nym_compact_ecash::{Base58, VerificationKeyAuth};
10use std::fmt::{Display, Formatter};
11use thiserror::Error;
12use url::Url;
13
14// TODO: it really doesn't feel like this should live in this crate.
15#[derive(Clone)]
16pub struct EcashApiClient {
17    pub api_client: nym_http_api_client::Client,
18    pub verification_key: VerificationKeyAuth,
19    pub node_id: NodeIndex,
20    pub cosmos_address: cosmrs::AccountId,
21}
22
23impl EcashApiClient {
24    pub fn try_construct_from_share(share: ContractVKShare) -> Result<Self, EcashApiError> {
25        if !share.verified {
26            return Err(EcashApiError::UnverifiedShare);
27        }
28
29        let url_address = Url::parse(&share.announce_address)?;
30
31        // The NymApiClient constructed here uses the default (hickory DoT/DoH) resolver because
32        // this EcashApiClient is used by both client and non-client applications.
33        //
34        // In non-client applications this resolver can cause warning logs about H2 connection
35        // failure. This indicates that the long lived https connection was closed by the remote
36        // peer and the resolver will have to reconnect. It should not impact actual functionality
37        let api_client = nym_http_api_client::Client::builder(url_address)
38            .map_err(|e| EcashApiError::ClientError(e.to_string()))?
39            .build()
40            .map_err(|e| EcashApiError::ClientError(e.to_string()))?;
41
42        Ok(EcashApiClient {
43            api_client,
44            verification_key: VerificationKeyAuth::try_from_bs58(&share.share)?,
45            node_id: share.node_index,
46            cosmos_address: share.owner.as_str().parse()?,
47        })
48    }
49}
50
51impl Display for EcashApiClient {
52    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
53        write!(
54            f,
55            "[id: {}] {} @ ({})",
56            self.node_id,
57            self.cosmos_address,
58            self.api_client
59                .base_urls()
60                .iter()
61                .map(|url| url.to_string())
62                .collect::<Vec<String>>()
63                .join(", ")
64        )
65    }
66}
67
68// TODO: this should be using the coconut error
69// (which is in different crate; perhaps this client should be moved there?)
70
71#[derive(Debug, Error)]
72pub enum EcashApiError {
73    // TODO: ask @BN whether this is a correct error message
74    #[error("the provided key share hasn't been verified")]
75    UnverifiedShare,
76
77    #[error("failed to query the contract: {source}")]
78    ContractQueryFailure {
79        #[from]
80        source: NyxdError,
81    },
82
83    #[error("the provided announce address is malformed: {source}")]
84    MalformedAnnounceAddress {
85        #[from]
86        source: url::ParseError,
87    },
88
89    #[error("the provided verification key is malformed: {source}")]
90    MalformedVerificationKey {
91        #[from]
92        source: CompactEcashError,
93    },
94
95    #[error("failed to create API client: {0}")]
96    ClientError(String),
97
98    #[error("the provided account address is malformed: {source}")]
99    MalformedAccountAddress {
100        #[from]
101        source: cosmrs::ErrorReport,
102    },
103
104    #[error("nym api error")]
105    NymApi {
106        #[from]
107        source: crate::ValidatorClientError,
108    },
109}
110
111pub async fn all_ecash_api_clients<C>(
112    client: &C,
113    epoch_id: EpochId,
114) -> Result<Vec<EcashApiClient>, EcashApiError>
115where
116    C: DkgQueryClient,
117{
118    // TODO: this will error out if there's an invalid share out there. is that what we want?
119    client
120        .get_all_verification_key_shares(epoch_id)
121        .await?
122        .into_iter()
123        .map(EcashApiClient::try_construct_from_share)
124        .collect::<Result<Vec<_>, _>>()
125
126    // ... if not, let's switch to the below:
127    // client
128    //     .get_all_verification_key_shares(epoch_id)
129    //     .await?
130    //     .into_iter()
131    //     .filter_map(TryInto::try_into)
132    //     .collect::<Result<Vec<_>, _>>()
133}