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