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