1use std::collections::BTreeMap;
2use std::sync::Arc;
3use std::time::Duration;
4
5use bitcoin_hashes::sha256;
6use fedimint_api_client::api::{DynModuleApi, FederationApiExt, FederationResult, ServerError};
7use fedimint_api_client::query::FilterMapThreshold;
8use fedimint_core::module::ApiRequestErased;
9use fedimint_core::util::FmtCompact as _;
10use fedimint_core::{NumPeersExt, OutPointRange, PeerId, apply, async_trait_maybe_send, runtime};
11use fedimint_mintv2_common::endpoint_constants::{
12 RECOVERY_COUNT_ENDPOINT, RECOVERY_SLICE_ENDPOINT, RECOVERY_SLICE_HASH_ENDPOINT,
13 SIGNATURE_SHARES_ENDPOINT, SIGNATURE_SHARES_RECOVERY_ENDPOINT,
14};
15use fedimint_mintv2_common::{Denomination, RecoveryItem};
16use tbs::{BlindedMessage, BlindedSignatureShare, PublicKeyShare};
17
18use crate::NoteIssuanceRequest;
19use crate::output::verify_blind_shares;
20
21#[apply(async_trait_maybe_send!)]
22pub trait MintV2ModuleApi {
23 async fn fetch_signature_shares(
24 &self,
25 range: OutPointRange,
26 issuance_requests: Vec<NoteIssuanceRequest>,
27 tbs_pks: BTreeMap<Denomination, BTreeMap<PeerId, PublicKeyShare>>,
28 ) -> BTreeMap<PeerId, Vec<BlindedSignatureShare>>;
29
30 async fn fetch_signature_shares_recovery(
31 &self,
32 issuance_requests: Vec<NoteIssuanceRequest>,
33 tbs_pks: BTreeMap<Denomination, BTreeMap<PeerId, PublicKeyShare>>,
34 ) -> BTreeMap<PeerId, Vec<BlindedSignatureShare>>;
35
36 async fn fetch_recovery_count(&self) -> FederationResult<u64>;
37
38 async fn fetch_recovery_slice_hash(&self, start: u64, end: u64) -> sha256::Hash;
39
40 async fn fetch_recovery_slice(
41 &self,
42 peer: PeerId,
43 timeout: Duration,
44 start: u64,
45 end: u64,
46 ) -> Result<Vec<RecoveryItem>, FetchRecoverySliceError>;
47}
48
49#[apply(async_trait_maybe_send!)]
50impl MintV2ModuleApi for DynModuleApi {
51 async fn fetch_signature_shares(
52 &self,
53 range: OutPointRange,
54 issuance_requests: Vec<NoteIssuanceRequest>,
55 tbs_pks: BTreeMap<Denomination, BTreeMap<PeerId, PublicKeyShare>>,
56 ) -> BTreeMap<PeerId, Vec<BlindedSignatureShare>> {
57 let issuance_requests = Arc::new(issuance_requests);
58 let tbs_pks = Arc::new(tbs_pks);
59
60 self.request_with_strategy_retry(
61 FilterMapThreshold::new(
63 move |peer, signature_shares: Vec<BlindedSignatureShare>| {
64 let issuance_requests = issuance_requests.clone();
65 let tbs_pks = tbs_pks.clone();
66
67 runtime::spawn_blocking(move || {
69 verify_blind_shares(peer, signature_shares, &issuance_requests, &tbs_pks)
70 .map_err(|err| {
71 ServerError::InvalidResponse(err.fmt_compact().to_string())
72 })
73 })
74 },
75 self.all_peers().to_num_peers(),
76 ),
77 SIGNATURE_SHARES_ENDPOINT.to_owned(),
78 ApiRequestErased::new(range),
79 )
80 .await
81 }
82
83 async fn fetch_signature_shares_recovery(
84 &self,
85 issuance_requests: Vec<NoteIssuanceRequest>,
86 tbs_pks: BTreeMap<Denomination, BTreeMap<PeerId, PublicKeyShare>>,
87 ) -> BTreeMap<PeerId, Vec<BlindedSignatureShare>> {
88 let blinded_messages: Vec<BlindedMessage> = issuance_requests
89 .iter()
90 .map(NoteIssuanceRequest::blinded_message)
91 .collect();
92
93 let issuance_requests = Arc::new(issuance_requests);
94 let tbs_pks = Arc::new(tbs_pks);
95
96 self.request_with_strategy_retry(
97 FilterMapThreshold::new(
99 move |peer, signature_shares: Vec<BlindedSignatureShare>| {
100 let issuance_requests = issuance_requests.clone();
101 let tbs_pks = tbs_pks.clone();
102
103 runtime::spawn_blocking(move || {
105 verify_blind_shares(peer, signature_shares, &issuance_requests, &tbs_pks)
106 .map_err(|err| {
107 ServerError::InvalidResponse(err.fmt_compact().to_string())
108 })
109 })
110 },
111 self.all_peers().to_num_peers(),
112 ),
113 SIGNATURE_SHARES_RECOVERY_ENDPOINT.to_owned(),
114 ApiRequestErased::new(blinded_messages),
115 )
116 .await
117 }
118
119 async fn fetch_recovery_count(&self) -> FederationResult<u64> {
120 self.request_current_consensus::<u64>(
121 RECOVERY_COUNT_ENDPOINT.to_string(),
122 ApiRequestErased::default(),
123 )
124 .await
125 }
126
127 async fn fetch_recovery_slice_hash(&self, start: u64, end: u64) -> sha256::Hash {
128 self.request_current_consensus_retry(
129 RECOVERY_SLICE_HASH_ENDPOINT.to_owned(),
130 ApiRequestErased::new((start, end)),
131 )
132 .await
133 }
134
135 async fn fetch_recovery_slice(
136 &self,
137 peer: PeerId,
138 timeout: Duration,
139 start: u64,
140 end: u64,
141 ) -> Result<Vec<RecoveryItem>, FetchRecoverySliceError> {
142 let result = runtime::timeout(
143 timeout,
144 self.request_single_peer::<Vec<RecoveryItem>>(
145 RECOVERY_SLICE_ENDPOINT.to_owned(),
146 ApiRequestErased::new((start, end)),
147 peer,
148 ),
149 )
150 .await??;
151
152 Ok(result)
153 }
154}
155
156#[derive(Debug, thiserror::Error)]
158pub(crate) enum FetchRecoverySliceError {
159 #[error(transparent)]
161 Timeout(#[from] runtime::Elapsed),
162
163 #[error(transparent)]
165 Peer(#[from] ServerError),
166}