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 anyhow::bail;
10use async_trait::async_trait;
11use fedimint_core::config::{
12 ServerModuleConfig, ServerModuleConsensusConfig, TypedServerModuleConfig,
13 TypedServerModuleConsensusConfig,
14};
15use fedimint_core::core::ModuleInstanceId;
16use fedimint_core::db::{DatabaseTransaction, DatabaseVersion, IDatabaseTransactionOpsCoreTyped};
17use fedimint_core::module::audit::Audit;
18use fedimint_core::module::{
19 Amounts, ApiEndpoint, CORE_CONSENSUS_VERSION, CoreConsensusVersion, InputMeta,
20 ModuleConsensusVersion, ModuleInit, SupportedModuleApiVersions, TransactionItemAmounts,
21};
22use fedimint_core::{Amount, InPoint, OutPoint, PeerId, push_db_pair_items};
23pub use fedimint_dummy_common as common;
24use fedimint_dummy_common::config::{
25 DummyClientConfig, DummyConfig, DummyConfigConsensus, DummyConfigPrivate,
26};
27use fedimint_dummy_common::{
28 DummyCommonInit, DummyConsensusItem, DummyInput, DummyInputError, DummyModuleTypes,
29 DummyOutput, DummyOutputError, DummyOutputOutcome, MODULE_CONSENSUS_VERSION,
30 broken_fed_public_key, fed_public_key,
31};
32use fedimint_server_core::config::PeerHandleOps;
33use fedimint_server_core::migration::ServerModuleDbMigrationFn;
34use fedimint_server_core::{
35 ConfigGenModuleArgs, ServerModule, ServerModuleInit, ServerModuleInitArgs,
36};
37use futures::{FutureExt, StreamExt};
38use strum::IntoEnumIterator;
39
40use crate::db::{
41 DbKeyPrefix, DummyFundsKeyV1, DummyFundsPrefixV1, DummyOutcomeKey, DummyOutcomePrefix,
42 migrate_to_v1, migrate_to_v2,
43};
44
45pub mod db;
46
47#[derive(Debug, Clone)]
49pub struct DummyInit;
50
51impl ModuleInit for DummyInit {
53 type Common = DummyCommonInit;
54
55 async fn dump_database(
57 &self,
58 dbtx: &mut DatabaseTransaction<'_>,
59 prefix_names: Vec<String>,
60 ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_> {
61 let mut items: BTreeMap<String, Box<dyn erased_serde::Serialize + Send>> = BTreeMap::new();
63 let filtered_prefixes = DbKeyPrefix::iter().filter(|f| {
64 prefix_names.is_empty() || prefix_names.contains(&f.to_string().to_lowercase())
65 });
66
67 for table in filtered_prefixes {
68 match table {
69 DbKeyPrefix::Funds => {
70 push_db_pair_items!(
71 dbtx,
72 DummyFundsPrefixV1,
73 DummyFundsKeyV1,
74 Amount,
75 items,
76 "Dummy Funds"
77 );
78 }
79 DbKeyPrefix::Outcome => {
80 push_db_pair_items!(
81 dbtx,
82 DummyOutcomePrefix,
83 DummyOutcomeKey,
84 DummyOutputOutcome,
85 items,
86 "Dummy Outputs"
87 );
88 }
89 }
90 }
91
92 Box::new(items.into_iter())
93 }
94}
95
96#[async_trait]
98impl ServerModuleInit for DummyInit {
99 type Module = Dummy;
100
101 fn versions(&self, _core: CoreConsensusVersion) -> &[ModuleConsensusVersion] {
103 &[MODULE_CONSENSUS_VERSION]
104 }
105
106 fn supported_api_versions(&self) -> SupportedModuleApiVersions {
107 SupportedModuleApiVersions::from_raw(
108 (CORE_CONSENSUS_VERSION.major, CORE_CONSENSUS_VERSION.minor),
109 (
110 MODULE_CONSENSUS_VERSION.major,
111 MODULE_CONSENSUS_VERSION.minor,
112 ),
113 &[(0, 0)],
114 )
115 }
116
117 async fn init(&self, args: &ServerModuleInitArgs<Self>) -> anyhow::Result<Self::Module> {
119 Ok(Dummy::new(args.cfg().to_typed()?))
120 }
121
122 fn trusted_dealer_gen(
124 &self,
125 peers: &[PeerId],
126 _args: &ConfigGenModuleArgs,
127 ) -> BTreeMap<PeerId, ServerModuleConfig> {
128 peers
130 .iter()
131 .map(|&peer| {
132 let config = DummyConfig {
133 private: DummyConfigPrivate,
134 consensus: DummyConfigConsensus {
135 tx_fee: Amount::ZERO,
136 },
137 };
138 (peer, config.to_erased())
139 })
140 .collect()
141 }
142
143 async fn distributed_gen(
145 &self,
146 _peers: &(dyn PeerHandleOps + Send + Sync),
147 _args: &ConfigGenModuleArgs,
148 ) -> anyhow::Result<ServerModuleConfig> {
149 Ok(DummyConfig {
150 private: DummyConfigPrivate,
151 consensus: DummyConfigConsensus {
152 tx_fee: Amount::ZERO,
153 },
154 }
155 .to_erased())
156 }
157
158 fn get_client_config(
160 &self,
161 config: &ServerModuleConsensusConfig,
162 ) -> anyhow::Result<DummyClientConfig> {
163 let config = DummyConfigConsensus::from_erased(config)?;
164 Ok(DummyClientConfig {
165 tx_fee: config.tx_fee,
166 })
167 }
168
169 fn validate_config(
170 &self,
171 _identity: &PeerId,
172 _config: ServerModuleConfig,
173 ) -> anyhow::Result<()> {
174 Ok(())
175 }
176
177 fn get_database_migrations(
179 &self,
180 ) -> BTreeMap<DatabaseVersion, ServerModuleDbMigrationFn<Dummy>> {
181 let mut migrations: BTreeMap<DatabaseVersion, ServerModuleDbMigrationFn<Dummy>> =
182 BTreeMap::new();
183 migrations.insert(
184 DatabaseVersion(0),
185 Box::new(|ctx| migrate_to_v1(ctx).boxed()),
186 );
187 migrations.insert(
188 DatabaseVersion(1),
189 Box::new(|ctx| migrate_to_v2(ctx).boxed()),
190 );
191 migrations
192 }
193}
194
195#[derive(Debug)]
197pub struct Dummy {
198 pub cfg: DummyConfig,
199}
200
201#[async_trait]
203impl ServerModule for Dummy {
204 type Common = DummyModuleTypes;
206 type Init = DummyInit;
207
208 async fn consensus_proposal(
209 &self,
210 _dbtx: &mut DatabaseTransaction<'_>,
211 ) -> Vec<DummyConsensusItem> {
212 Vec::new()
213 }
214
215 async fn process_consensus_item<'a, 'b>(
216 &'a self,
217 _dbtx: &mut DatabaseTransaction<'b>,
218 _consensus_item: DummyConsensusItem,
219 _peer_id: PeerId,
220 ) -> anyhow::Result<()> {
221 bail!("The dummy module does not use consensus items");
227 }
228
229 async fn process_input<'a, 'b, 'c>(
230 &'a self,
231 dbtx: &mut DatabaseTransaction<'c>,
232 input: &'b DummyInput,
233 _in_point: InPoint,
234 ) -> Result<InputMeta, DummyInputError> {
235 let DummyInput::V0(input) = input else {
236 return Err(DummyInputError::InvalidVersion);
237 };
238 let current_funds = dbtx
239 .get_value(&DummyFundsKeyV1(input.account))
240 .await
241 .unwrap_or(Amount::ZERO);
242
243 if input.amount > current_funds
245 && fed_public_key() != input.account
246 && broken_fed_public_key() != input.account
247 {
248 return Err(DummyInputError::NotEnoughFunds);
249 }
250
251 let updated_funds = if fed_public_key() == input.account {
253 current_funds + input.amount
254 } else if broken_fed_public_key() == input.account {
255 current_funds
257 } else {
258 current_funds.saturating_sub(input.amount)
259 };
260
261 dbtx.insert_entry(&DummyFundsKeyV1(input.account), &updated_funds)
262 .await;
263
264 Ok(InputMeta {
265 amount: TransactionItemAmounts {
266 amounts: Amounts::new_bitcoin(input.amount),
267 fees: Amounts::new_bitcoin(self.cfg.consensus.tx_fee),
268 },
269 pub_key: input.account,
271 })
272 }
273
274 async fn process_output<'a, 'b>(
275 &'a self,
276 dbtx: &mut DatabaseTransaction<'b>,
277 output: &'a DummyOutput,
278 out_point: OutPoint,
279 ) -> Result<TransactionItemAmounts, DummyOutputError> {
280 let DummyOutput::V0(output) = output else {
281 return Err(DummyOutputError::InvalidVersion);
282 };
283 let current_funds = dbtx.get_value(&DummyFundsKeyV1(output.account)).await;
285 let updated_funds = current_funds.unwrap_or(Amount::ZERO) + output.amount;
286 dbtx.insert_entry(&DummyFundsKeyV1(output.account), &updated_funds)
287 .await;
288
289 let outcome = DummyOutputOutcome(updated_funds, output.unit, output.account);
291 dbtx.insert_entry(&DummyOutcomeKey(out_point), &outcome)
292 .await;
293
294 Ok(TransactionItemAmounts {
295 amounts: Amounts::new_bitcoin(output.amount),
296 fees: Amounts::new_bitcoin(self.cfg.consensus.tx_fee),
297 })
298 }
299
300 async fn output_status(
301 &self,
302 dbtx: &mut DatabaseTransaction<'_>,
303 out_point: OutPoint,
304 ) -> Option<DummyOutputOutcome> {
305 dbtx.get_value(&DummyOutcomeKey(out_point)).await
307 }
308
309 async fn audit(
310 &self,
311 dbtx: &mut DatabaseTransaction<'_>,
312 audit: &mut Audit,
313 module_instance_id: ModuleInstanceId,
314 ) {
315 audit
316 .add_items(
317 dbtx,
318 module_instance_id,
319 &DummyFundsPrefixV1,
320 |k, v| match k {
321 DummyFundsKeyV1(key)
324 if key == fed_public_key() || key == broken_fed_public_key() =>
325 {
326 v.msats as i64
327 }
328 DummyFundsKeyV1(_) => -(v.msats as i64),
330 },
331 )
332 .await;
333 }
334
335 fn api_endpoints(&self) -> Vec<ApiEndpoint<Self>> {
336 Vec::new()
337 }
338}
339
340impl Dummy {
341 pub fn new(cfg: DummyConfig) -> Dummy {
343 Dummy { cfg }
344 }
345}