Skip to main content

fedimint_mint_client/
api.rs

1use fedimint_api_client::api::{FederationApiExt, FederationResult, IModuleFederationApi};
2use fedimint_core::bitcoin::hashes::sha256;
3use fedimint_core::module::registry::ModuleRegistry;
4use fedimint_core::module::{ApiRequestErased, SerdeModuleEncodingBase64};
5use fedimint_core::task::{MaybeSend, MaybeSync};
6use fedimint_core::{OutPoint, PeerId, apply, async_trait_maybe_send};
7use fedimint_mint_common::endpoint_constants::{
8    BLIND_NONCE_USED_ENDPOINT, NOTE_SPENT_ENDPOINT, RECOVERY_BLIND_NONCE_OUTPOINTS_ENDPOINT,
9    RECOVERY_COUNT_ENDPOINT, RECOVERY_SLICE_ENDPOINT, RECOVERY_SLICE_HASH_ENDPOINT,
10};
11use fedimint_mint_common::{BlindNonce, Nonce, RecoveryItem};
12
13#[apply(async_trait_maybe_send!)]
14pub trait MintFederationApi {
15    async fn check_blind_nonce_used(&self, blind_nonce: BlindNonce) -> FederationResult<bool>;
16
17    async fn check_note_spent(&self, nonce: Nonce) -> FederationResult<bool>;
18
19    /// Asks a single peer if e-cash has already been issued for `blind_nonce`.
20    ///
21    /// Unlike [`MintFederationApi::check_blind_nonce_used`] this does not wait
22    /// for a threshold of peers to agree, which makes it possible to observe
23    /// disagreement between peers.
24    async fn check_blind_nonce_used_single_peer(
25        &self,
26        peer: PeerId,
27        blind_nonce: BlindNonce,
28    ) -> anyhow::Result<bool>;
29
30    /// Asks a single peer if the note identified by `nonce` was already spent.
31    ///
32    /// Unlike [`MintFederationApi::check_note_spent`] this does not wait for a
33    /// threshold of peers to agree, which makes it possible to observe
34    /// disagreement between peers.
35    async fn check_note_spent_single_peer(
36        &self,
37        peer: PeerId,
38        nonce: Nonce,
39    ) -> anyhow::Result<bool>;
40
41    /// Returns the total number of recovery items stored on the federation.
42    async fn fetch_recovery_count(&self) -> anyhow::Result<u64>;
43
44    /// Returns the consensus hash of recovery items in the range `[start,
45    /// end)`.
46    async fn fetch_recovery_slice_hash(&self, start: u64, end: u64) -> sha256::Hash;
47
48    /// Fetches recovery items in the range `[start, end)` from a specific peer.
49    async fn fetch_recovery_slice(
50        &self,
51        peer: PeerId,
52        start: u64,
53        end: u64,
54    ) -> anyhow::Result<Vec<RecoveryItem>>;
55
56    /// Returns the outpoints where the given blind nonces were used.
57    async fn fetch_blind_nonce_outpoints(
58        &self,
59        blind_nonces: Vec<BlindNonce>,
60    ) -> anyhow::Result<Vec<OutPoint>>;
61}
62
63#[apply(async_trait_maybe_send!)]
64impl<T: ?Sized> MintFederationApi for T
65where
66    T: IModuleFederationApi + MaybeSend + MaybeSync + 'static,
67{
68    async fn check_blind_nonce_used(&self, blind_nonce: BlindNonce) -> FederationResult<bool> {
69        self.request_current_consensus(
70            BLIND_NONCE_USED_ENDPOINT.to_string(),
71            ApiRequestErased::new(blind_nonce),
72        )
73        .await
74    }
75
76    async fn check_note_spent(&self, nonce: Nonce) -> FederationResult<bool> {
77        self.request_current_consensus(
78            NOTE_SPENT_ENDPOINT.to_string(),
79            ApiRequestErased::new(nonce),
80        )
81        .await
82    }
83
84    async fn check_blind_nonce_used_single_peer(
85        &self,
86        peer: PeerId,
87        blind_nonce: BlindNonce,
88    ) -> anyhow::Result<bool> {
89        Ok(self
90            .request_single_peer::<bool>(
91                BLIND_NONCE_USED_ENDPOINT.to_string(),
92                ApiRequestErased::new(blind_nonce),
93                peer,
94            )
95            .await?)
96    }
97
98    async fn check_note_spent_single_peer(
99        &self,
100        peer: PeerId,
101        nonce: Nonce,
102    ) -> anyhow::Result<bool> {
103        Ok(self
104            .request_single_peer::<bool>(
105                NOTE_SPENT_ENDPOINT.to_string(),
106                ApiRequestErased::new(nonce),
107                peer,
108            )
109            .await?)
110    }
111
112    async fn fetch_recovery_count(&self) -> anyhow::Result<u64> {
113        self.request_current_consensus::<u64>(
114            RECOVERY_COUNT_ENDPOINT.to_string(),
115            ApiRequestErased::default(),
116        )
117        .await
118        .map_err(|e| anyhow::anyhow!("{e}"))
119    }
120
121    async fn fetch_recovery_slice_hash(&self, start: u64, end: u64) -> sha256::Hash {
122        self.request_current_consensus_retry(
123            RECOVERY_SLICE_HASH_ENDPOINT.to_owned(),
124            ApiRequestErased::new((start, end)),
125        )
126        .await
127    }
128
129    async fn fetch_recovery_slice(
130        &self,
131        peer: PeerId,
132        start: u64,
133        end: u64,
134    ) -> anyhow::Result<Vec<RecoveryItem>> {
135        let result = self
136            .request_single_peer::<SerdeModuleEncodingBase64<Vec<RecoveryItem>>>(
137                RECOVERY_SLICE_ENDPOINT.to_owned(),
138                ApiRequestErased::new((start, end)),
139                peer,
140            )
141            .await?;
142
143        Ok(result.try_into_inner(&ModuleRegistry::default())?)
144    }
145
146    async fn fetch_blind_nonce_outpoints(
147        &self,
148        blind_nonces: Vec<BlindNonce>,
149    ) -> anyhow::Result<Vec<OutPoint>> {
150        self.request_current_consensus::<Vec<OutPoint>>(
151            RECOVERY_BLIND_NONCE_OUTPOINTS_ENDPOINT.to_string(),
152            ApiRequestErased::new(blind_nonces),
153        )
154        .await
155        .map_err(|e| anyhow::anyhow!("{e}"))
156    }
157}