Skip to main content

fedimint_mintv2_client/
api.rs

1use std::collections::BTreeMap;
2use std::time::Duration;
3
4use bitcoin_hashes::sha256;
5use fedimint_api_client::api::{DynModuleApi, FederationApiExt, ServerError};
6use fedimint_api_client::query::FilterMapThreshold;
7use fedimint_core::module::ApiRequestErased;
8use fedimint_core::util::FmtCompactAnyhow as _;
9use fedimint_core::{NumPeersExt, OutPointRange, PeerId, apply, async_trait_maybe_send, runtime};
10use fedimint_mintv2_common::endpoint_constants::{
11    RECOVERY_COUNT_ENDPOINT, RECOVERY_SLICE_ENDPOINT, RECOVERY_SLICE_HASH_ENDPOINT,
12    SIGNATURE_SHARES_ENDPOINT, SIGNATURE_SHARES_RECOVERY_ENDPOINT,
13};
14use fedimint_mintv2_common::{Denomination, RecoveryItem};
15use tbs::{BlindedMessage, BlindedSignatureShare, PublicKeyShare};
16
17use crate::NoteIssuanceRequest;
18use crate::output::verify_blind_shares;
19
20#[apply(async_trait_maybe_send!)]
21pub trait MintV2ModuleApi {
22    async fn fetch_signature_shares(
23        &self,
24        range: OutPointRange,
25        issuance_requests: Vec<NoteIssuanceRequest>,
26        tbs_pks: BTreeMap<Denomination, BTreeMap<PeerId, PublicKeyShare>>,
27    ) -> BTreeMap<PeerId, Vec<BlindedSignatureShare>>;
28
29    async fn fetch_signature_shares_recovery(
30        &self,
31        issuance_requests: Vec<NoteIssuanceRequest>,
32        tbs_pks: BTreeMap<Denomination, BTreeMap<PeerId, PublicKeyShare>>,
33    ) -> BTreeMap<PeerId, Vec<BlindedSignatureShare>>;
34
35    async fn fetch_recovery_count(&self) -> anyhow::Result<u64>;
36
37    async fn fetch_recovery_slice_hash(&self, start: u64, end: u64) -> sha256::Hash;
38
39    async fn fetch_recovery_slice(
40        &self,
41        peer: PeerId,
42        timeout: Duration,
43        start: u64,
44        end: u64,
45    ) -> anyhow::Result<Vec<RecoveryItem>>;
46}
47
48#[apply(async_trait_maybe_send!)]
49impl MintV2ModuleApi for DynModuleApi {
50    async fn fetch_signature_shares(
51        &self,
52        range: OutPointRange,
53        issuance_requests: Vec<NoteIssuanceRequest>,
54        tbs_pks: BTreeMap<Denomination, BTreeMap<PeerId, PublicKeyShare>>,
55    ) -> BTreeMap<PeerId, Vec<BlindedSignatureShare>> {
56        self.request_with_strategy_retry(
57            // This query collects a threshold of 2f + 1 valid blind signature shares
58            FilterMapThreshold::new(
59                move |peer, signature_shares| {
60                    verify_blind_shares(peer, signature_shares, &issuance_requests, &tbs_pks)
61                        .map_err(|err| {
62                            ServerError::InvalidResponse(err.fmt_compact_anyhow().to_string())
63                        })
64                },
65                self.all_peers().to_num_peers(),
66            ),
67            SIGNATURE_SHARES_ENDPOINT.to_owned(),
68            ApiRequestErased::new(range),
69        )
70        .await
71    }
72
73    async fn fetch_signature_shares_recovery(
74        &self,
75        issuance_requests: Vec<NoteIssuanceRequest>,
76        tbs_pks: BTreeMap<Denomination, BTreeMap<PeerId, PublicKeyShare>>,
77    ) -> BTreeMap<PeerId, Vec<BlindedSignatureShare>> {
78        let blinded_messages: Vec<BlindedMessage> = issuance_requests
79            .iter()
80            .map(NoteIssuanceRequest::blinded_message)
81            .collect();
82
83        self.request_with_strategy_retry(
84            // This query collects a threshold of 2f + 1 valid blind signature shares
85            FilterMapThreshold::new(
86                move |peer, signature_shares| {
87                    verify_blind_shares(peer, signature_shares, &issuance_requests, &tbs_pks)
88                        .map_err(|err| {
89                            ServerError::InvalidResponse(err.fmt_compact_anyhow().to_string())
90                        })
91                },
92                self.all_peers().to_num_peers(),
93            ),
94            SIGNATURE_SHARES_RECOVERY_ENDPOINT.to_owned(),
95            ApiRequestErased::new(blinded_messages),
96        )
97        .await
98    }
99
100    async fn fetch_recovery_count(&self) -> anyhow::Result<u64> {
101        self.request_current_consensus::<u64>(
102            RECOVERY_COUNT_ENDPOINT.to_string(),
103            ApiRequestErased::default(),
104        )
105        .await
106        .map_err(|e| anyhow::anyhow!("{e}"))
107    }
108
109    async fn fetch_recovery_slice_hash(&self, start: u64, end: u64) -> sha256::Hash {
110        self.request_current_consensus_retry(
111            RECOVERY_SLICE_HASH_ENDPOINT.to_owned(),
112            ApiRequestErased::new((start, end)),
113        )
114        .await
115    }
116
117    async fn fetch_recovery_slice(
118        &self,
119        peer: PeerId,
120        timeout: Duration,
121        start: u64,
122        end: u64,
123    ) -> anyhow::Result<Vec<RecoveryItem>> {
124        let result = runtime::timeout(
125            timeout,
126            self.request_single_peer::<Vec<RecoveryItem>>(
127                RECOVERY_SLICE_ENDPOINT.to_owned(),
128                ApiRequestErased::new((start, end)),
129                peer,
130            ),
131        )
132        .await??;
133
134        Ok(result)
135    }
136}