Skip to main content

fedimint_dummy_server/
lib.rs

1#![deny(clippy::pedantic)]
2#![allow(clippy::cast_possible_wrap)]
3#![allow(clippy::missing_errors_doc)]
4#![allow(clippy::module_name_repetitions)]
5#![allow(clippy::must_use_candidate)]
6
7use std::collections::BTreeMap;
8
9use async_trait::async_trait;
10use fedimint_core::config::{
11    ServerModuleConfig, ServerModuleConsensusConfig, TypedServerModuleConfig,
12};
13use fedimint_core::core::ModuleInstanceId;
14use fedimint_core::db::{DatabaseTransaction, DatabaseVersion, IDatabaseTransactionOpsCoreTyped};
15use fedimint_core::module::audit::Audit;
16use fedimint_core::module::{
17    Amounts, ApiEndpoint, CORE_CONSENSUS_VERSION, CoreConsensusVersion, InputMeta,
18    ModuleConsensusVersion, ModuleInit, SupportedModuleApiVersions, TransactionItemAmounts,
19};
20use fedimint_core::{Amount, InPoint, OutPoint, PeerId, push_db_pair_items};
21pub use fedimint_dummy_common as common;
22use fedimint_dummy_common::config::{
23    DummyClientConfig, DummyConfig, DummyConfigConsensus, DummyConfigPrivate,
24};
25use fedimint_dummy_common::{
26    DummyCommonInit, DummyConsensusItem, DummyInput, DummyInputError, DummyModuleTypes,
27    DummyOutput, DummyOutputError, DummyOutputOutcome, MODULE_CONSENSUS_VERSION,
28};
29use fedimint_server_core::config::PeerHandleOps;
30use fedimint_server_core::migration::ServerModuleDbMigrationFn;
31use fedimint_server_core::{
32    ConfigGenModuleArgs, ServerModule, ServerModuleInit, ServerModuleInitArgs,
33};
34use futures::StreamExt;
35use strum::IntoEnumIterator;
36
37use crate::db::{
38    DbKeyPrefix, DummyInputAuditKey, DummyInputAuditPrefix, DummyOutputAuditKey,
39    DummyOutputAuditPrefix,
40};
41
42pub mod db;
43
44/// Generates the module
45#[derive(Debug, Clone)]
46pub struct DummyInit;
47
48impl ModuleInit for DummyInit {
49    type Common = DummyCommonInit;
50
51    /// Dumps all database items for debugging
52    async fn dump_database(
53        &self,
54        dbtx: &mut DatabaseTransaction<'_>,
55        prefix_names: Vec<String>,
56    ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_> {
57        let mut items: BTreeMap<String, Box<dyn erased_serde::Serialize + Send>> = BTreeMap::new();
58        let filtered_prefixes = DbKeyPrefix::iter().filter(|f| {
59            prefix_names.is_empty() || prefix_names.contains(&f.to_string().to_lowercase())
60        });
61
62        for table in filtered_prefixes {
63            match table {
64                DbKeyPrefix::InputAudit => {
65                    push_db_pair_items!(
66                        dbtx,
67                        DummyInputAuditPrefix,
68                        DummyInputAuditKey,
69                        Amount,
70                        items,
71                        "Dummy Input Audit"
72                    );
73                }
74                DbKeyPrefix::OutputAudit => {
75                    push_db_pair_items!(
76                        dbtx,
77                        DummyOutputAuditPrefix,
78                        DummyOutputAuditKey,
79                        Amount,
80                        items,
81                        "Dummy Output Audit"
82                    );
83                }
84            }
85        }
86
87        Box::new(items.into_iter())
88    }
89}
90
91/// Implementation of server module non-consensus functions
92#[async_trait]
93impl ServerModuleInit for DummyInit {
94    type Module = Dummy;
95
96    /// Returns the version of this module
97    fn versions(&self, _core: CoreConsensusVersion) -> &[ModuleConsensusVersion] {
98        &[MODULE_CONSENSUS_VERSION]
99    }
100
101    fn supported_api_versions(&self) -> SupportedModuleApiVersions {
102        SupportedModuleApiVersions::from_raw(
103            (CORE_CONSENSUS_VERSION.major, CORE_CONSENSUS_VERSION.minor),
104            (
105                MODULE_CONSENSUS_VERSION.major,
106                MODULE_CONSENSUS_VERSION.minor,
107            ),
108            &[(0, 0)],
109        )
110    }
111
112    /// Initialize the module
113    async fn init(&self, args: &ServerModuleInitArgs<Self>) -> anyhow::Result<Self::Module> {
114        Ok(Dummy::new(args.cfg().to_typed()?))
115    }
116
117    /// Generates configs for all peers in a trusted manner for testing
118    fn trusted_dealer_gen(
119        &self,
120        peers: &[PeerId],
121        _args: &ConfigGenModuleArgs,
122    ) -> BTreeMap<PeerId, ServerModuleConfig> {
123        // Generate a config for each peer
124        peers
125            .iter()
126            .map(|&peer| {
127                let config = DummyConfig {
128                    private: DummyConfigPrivate,
129                    consensus: DummyConfigConsensus,
130                };
131                (peer, config.to_erased())
132            })
133            .collect()
134    }
135
136    /// Generates configs for all peers in an untrusted manner
137    async fn distributed_gen(
138        &self,
139        _peers: &(dyn PeerHandleOps + Send + Sync),
140        _args: &ConfigGenModuleArgs,
141    ) -> anyhow::Result<ServerModuleConfig> {
142        Ok(DummyConfig {
143            private: DummyConfigPrivate,
144            consensus: DummyConfigConsensus,
145        }
146        .to_erased())
147    }
148
149    /// Converts the consensus config into the client config
150    fn get_client_config(
151        &self,
152        _config: &ServerModuleConsensusConfig,
153    ) -> anyhow::Result<DummyClientConfig> {
154        Ok(DummyClientConfig)
155    }
156
157    fn validate_config(
158        &self,
159        _identity: &PeerId,
160        _config: ServerModuleConfig,
161    ) -> anyhow::Result<()> {
162        Ok(())
163    }
164
165    /// DB migrations to move from old to newer versions
166    fn get_database_migrations(
167        &self,
168    ) -> BTreeMap<DatabaseVersion, ServerModuleDbMigrationFn<Dummy>> {
169        BTreeMap::new()
170    }
171}
172
173/// Dummy module
174#[derive(Debug)]
175pub struct Dummy {
176    pub cfg: DummyConfig,
177}
178
179/// Implementation of consensus for the server module
180#[async_trait]
181impl ServerModule for Dummy {
182    /// Define the consensus types
183    type Common = DummyModuleTypes;
184    type Init = DummyInit;
185
186    async fn consensus_proposal(
187        &self,
188        _dbtx: &mut DatabaseTransaction<'_>,
189    ) -> Vec<DummyConsensusItem> {
190        Vec::new()
191    }
192
193    async fn process_consensus_item<'a, 'b>(
194        &'a self,
195        _dbtx: &mut DatabaseTransaction<'b>,
196        _consensus_item: DummyConsensusItem,
197        _peer_id: PeerId,
198    ) -> anyhow::Result<()> {
199        // WARNING: `process_consensus_item` should return an `Err` for items that do
200        // not change any internal consensus state. Failure to do so, will result in an
201        // (potentially significantly) increased consensus history size.
202        // If you are using this code as a template,
203        // make sure to read the [`ServerModule::process_consensus_item`] documentation,
204        anyhow::bail!("The dummy module does not use consensus items");
205    }
206
207    async fn process_input<'a, 'b, 'c>(
208        &'a self,
209        dbtx: &mut DatabaseTransaction<'c>,
210        input: &'b DummyInput,
211        in_point: InPoint,
212    ) -> Result<InputMeta, DummyInputError> {
213        dbtx.insert_entry(&DummyInputAuditKey(in_point), &input.amount)
214            .await;
215
216        Ok(InputMeta {
217            amount: TransactionItemAmounts {
218                amounts: Amounts::new_bitcoin(input.amount),
219                fees: Amounts::ZERO,
220            },
221            pub_key: input.pub_key,
222        })
223    }
224
225    async fn process_output<'a, 'b>(
226        &'a self,
227        dbtx: &mut DatabaseTransaction<'b>,
228        output: &'a DummyOutput,
229        out_point: OutPoint,
230    ) -> Result<TransactionItemAmounts, DummyOutputError> {
231        dbtx.insert_entry(&DummyOutputAuditKey(out_point), &output.amount)
232            .await;
233
234        Ok(TransactionItemAmounts {
235            amounts: Amounts::new_bitcoin(output.amount),
236            fees: Amounts::ZERO,
237        })
238    }
239
240    async fn output_status(
241        &self,
242        _dbtx: &mut DatabaseTransaction<'_>,
243        _out_point: OutPoint,
244    ) -> Option<DummyOutputOutcome> {
245        None
246    }
247
248    async fn audit(
249        &self,
250        dbtx: &mut DatabaseTransaction<'_>,
251        audit: &mut Audit,
252        module_instance_id: ModuleInstanceId,
253    ) {
254        // Inputs are assets (positive)
255        audit
256            .add_items(dbtx, module_instance_id, &DummyInputAuditPrefix, |_, v| {
257                v.msats as i64
258            })
259            .await;
260
261        // Outputs are liabilities (negative)
262        audit
263            .add_items(dbtx, module_instance_id, &DummyOutputAuditPrefix, |_, v| {
264                -(v.msats as i64)
265            })
266            .await;
267    }
268
269    fn api_endpoints(&self) -> Vec<ApiEndpoint<Self>> {
270        Vec::new()
271    }
272}
273
274impl Dummy {
275    /// Create new module instance
276    pub fn new(cfg: DummyConfig) -> Dummy {
277        Dummy { cfg }
278    }
279}