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, CoreConsensusVersion, InputMeta, ModuleConsensusVersion, ModuleInit,
18    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    /// Initialize the module
102    async fn init(&self, args: &ServerModuleInitArgs<Self>) -> anyhow::Result<Self::Module> {
103        Ok(Dummy::new(args.cfg().to_typed()?))
104    }
105
106    /// Generates configs for all peers in a trusted manner for testing
107    fn trusted_dealer_gen(
108        &self,
109        peers: &[PeerId],
110        _args: &ConfigGenModuleArgs,
111    ) -> BTreeMap<PeerId, ServerModuleConfig> {
112        // Generate a config for each peer
113        peers
114            .iter()
115            .map(|&peer| {
116                let config = DummyConfig {
117                    private: DummyConfigPrivate,
118                    consensus: DummyConfigConsensus,
119                };
120                (peer, config.to_erased())
121            })
122            .collect()
123    }
124
125    /// Generates configs for all peers in an untrusted manner
126    async fn distributed_gen(
127        &self,
128        _peers: &(dyn PeerHandleOps + Send + Sync),
129        _args: &ConfigGenModuleArgs,
130    ) -> anyhow::Result<ServerModuleConfig> {
131        Ok(DummyConfig {
132            private: DummyConfigPrivate,
133            consensus: DummyConfigConsensus,
134        }
135        .to_erased())
136    }
137
138    /// Converts the consensus config into the client config
139    fn get_client_config(
140        &self,
141        _config: &ServerModuleConsensusConfig,
142    ) -> anyhow::Result<DummyClientConfig> {
143        Ok(DummyClientConfig)
144    }
145
146    fn validate_config(
147        &self,
148        _identity: &PeerId,
149        _config: ServerModuleConfig,
150    ) -> anyhow::Result<()> {
151        Ok(())
152    }
153
154    /// DB migrations to move from old to newer versions
155    fn get_database_migrations(
156        &self,
157    ) -> BTreeMap<DatabaseVersion, ServerModuleDbMigrationFn<Dummy>> {
158        BTreeMap::new()
159    }
160}
161
162/// Dummy module
163#[derive(Debug)]
164pub struct Dummy {
165    pub cfg: DummyConfig,
166}
167
168/// Implementation of consensus for the server module
169#[async_trait]
170impl ServerModule for Dummy {
171    /// Define the consensus types
172    type Common = DummyModuleTypes;
173    type Init = DummyInit;
174
175    async fn consensus_proposal(
176        &self,
177        _dbtx: &mut DatabaseTransaction<'_>,
178    ) -> Vec<DummyConsensusItem> {
179        Vec::new()
180    }
181
182    async fn process_consensus_item<'a, 'b>(
183        &'a self,
184        _dbtx: &mut DatabaseTransaction<'b>,
185        _consensus_item: DummyConsensusItem,
186        _peer_id: PeerId,
187    ) -> anyhow::Result<()> {
188        // WARNING: `process_consensus_item` should return an `Err` for items that do
189        // not change any internal consensus state. Failure to do so, will result in an
190        // (potentially significantly) increased consensus history size.
191        // If you are using this code as a template,
192        // make sure to read the [`ServerModule::process_consensus_item`] documentation,
193        anyhow::bail!("The dummy module does not use consensus items");
194    }
195
196    async fn process_input<'a, 'b, 'c>(
197        &'a self,
198        dbtx: &mut DatabaseTransaction<'c>,
199        input: &'b DummyInput,
200        in_point: InPoint,
201    ) -> Result<InputMeta, DummyInputError> {
202        dbtx.insert_entry(&DummyInputAuditKey(in_point), &input.amount)
203            .await;
204
205        Ok(InputMeta {
206            amount: TransactionItemAmounts {
207                amounts: Amounts::new_bitcoin(input.amount),
208                fees: Amounts::ZERO,
209            },
210            pub_key: input.pub_key,
211        })
212    }
213
214    async fn process_output<'a, 'b>(
215        &'a self,
216        dbtx: &mut DatabaseTransaction<'b>,
217        output: &'a DummyOutput,
218        out_point: OutPoint,
219    ) -> Result<TransactionItemAmounts, DummyOutputError> {
220        dbtx.insert_entry(&DummyOutputAuditKey(out_point), &output.amount)
221            .await;
222
223        Ok(TransactionItemAmounts {
224            amounts: Amounts::new_bitcoin(output.amount),
225            fees: Amounts::ZERO,
226        })
227    }
228
229    async fn output_status(
230        &self,
231        _dbtx: &mut DatabaseTransaction<'_>,
232        _out_point: OutPoint,
233    ) -> Option<DummyOutputOutcome> {
234        None
235    }
236
237    async fn audit(
238        &self,
239        dbtx: &mut DatabaseTransaction<'_>,
240        audit: &mut Audit,
241        module_instance_id: ModuleInstanceId,
242    ) {
243        // Inputs are assets (positive)
244        audit
245            .add_items(dbtx, module_instance_id, &DummyInputAuditPrefix, |_, v| {
246                v.msats as i64
247            })
248            .await;
249
250        // Outputs are liabilities (negative)
251        audit
252            .add_items(dbtx, module_instance_id, &DummyOutputAuditPrefix, |_, v| {
253                -(v.msats as i64)
254            })
255            .await;
256    }
257
258    fn api_endpoints(&self) -> Vec<ApiEndpoint<Self>> {
259        Vec::new()
260    }
261}
262
263impl Dummy {
264    /// Create new module instance
265    pub fn new(cfg: DummyConfig) -> Dummy {
266        Dummy { cfg }
267    }
268}