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 ConfigGenModuleParams, DkgResult, ServerModuleConfig, ServerModuleConsensusConfig,
13 TypedServerModuleConfig, TypedServerModuleConsensusConfig,
14};
15use fedimint_core::core::ModuleInstanceId;
16use fedimint_core::db::{
17 CoreMigrationFn, DatabaseTransaction, DatabaseVersion, IDatabaseTransactionOpsCoreTyped,
18};
19use fedimint_core::module::audit::Audit;
20use fedimint_core::module::{
21 ApiEndpoint, CoreConsensusVersion, InputMeta, ModuleConsensusVersion, ModuleInit, PeerHandle,
22 ServerModuleInit, ServerModuleInitArgs, SupportedModuleApiVersions, TransactionItemAmount,
23 CORE_CONSENSUS_VERSION,
24};
25use fedimint_core::server::DynServerModule;
26use fedimint_core::{push_db_pair_items, Amount, OutPoint, PeerId, ServerModule};
27use fedimint_dummy_common::config::{
28 DummyClientConfig, DummyConfig, DummyConfigConsensus, DummyConfigLocal, DummyConfigPrivate,
29 DummyGenParams,
30};
31use fedimint_dummy_common::{
32 broken_fed_public_key, fed_public_key, DummyCommonInit, DummyConsensusItem, DummyInput,
33 DummyInputError, DummyModuleTypes, DummyOutput, DummyOutputError, DummyOutputOutcome,
34 MODULE_CONSENSUS_VERSION,
35};
36use futures::{FutureExt, StreamExt};
37use strum::IntoEnumIterator;
38
39use crate::db::{
40 migrate_to_v1, DbKeyPrefix, DummyFundsKeyV1, DummyFundsPrefixV1, DummyOutcomeKey,
41 DummyOutcomePrefix,
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 Params = DummyGenParams;
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<DynServerModule> {
118 Ok(Dummy::new(args.cfg().to_typed()?).into())
119 }
120
121 fn trusted_dealer_gen(
123 &self,
124 peers: &[PeerId],
125 params: &ConfigGenModuleParams,
126 ) -> BTreeMap<PeerId, ServerModuleConfig> {
127 let params = self.parse_params(params).unwrap();
128 peers
130 .iter()
131 .map(|&peer| {
132 let config = DummyConfig {
133 local: DummyConfigLocal {},
134 private: DummyConfigPrivate,
135 consensus: DummyConfigConsensus {
136 tx_fee: params.consensus.tx_fee,
137 },
138 };
139 (peer, config.to_erased())
140 })
141 .collect()
142 }
143
144 async fn distributed_gen(
146 &self,
147 _peers: &PeerHandle,
148 params: &ConfigGenModuleParams,
149 ) -> DkgResult<ServerModuleConfig> {
150 let params = self.parse_params(params).unwrap();
151
152 Ok(DummyConfig {
153 local: DummyConfigLocal {},
154 private: DummyConfigPrivate,
155 consensus: DummyConfigConsensus {
156 tx_fee: params.consensus.tx_fee,
157 },
158 }
159 .to_erased())
160 }
161
162 fn get_client_config(
164 &self,
165 config: &ServerModuleConsensusConfig,
166 ) -> anyhow::Result<DummyClientConfig> {
167 let config = DummyConfigConsensus::from_erased(config)?;
168 Ok(DummyClientConfig {
169 tx_fee: config.tx_fee,
170 })
171 }
172
173 fn validate_config(
174 &self,
175 _identity: &PeerId,
176 _config: ServerModuleConfig,
177 ) -> anyhow::Result<()> {
178 Ok(())
179 }
180
181 fn get_database_migrations(&self) -> BTreeMap<DatabaseVersion, CoreMigrationFn> {
183 let mut migrations: BTreeMap<DatabaseVersion, CoreMigrationFn> = BTreeMap::new();
184 migrations.insert(DatabaseVersion(0), |ctx| migrate_to_v1(ctx).boxed());
185 migrations
186 }
187}
188
189#[derive(Debug)]
191pub struct Dummy {
192 pub cfg: DummyConfig,
193}
194
195#[async_trait]
197impl ServerModule for Dummy {
198 type Common = DummyModuleTypes;
200 type Init = DummyInit;
201
202 async fn consensus_proposal(
203 &self,
204 _dbtx: &mut DatabaseTransaction<'_>,
205 ) -> Vec<DummyConsensusItem> {
206 Vec::new()
207 }
208
209 async fn process_consensus_item<'a, 'b>(
210 &'a self,
211 _dbtx: &mut DatabaseTransaction<'b>,
212 _consensus_item: DummyConsensusItem,
213 _peer_id: PeerId,
214 ) -> anyhow::Result<()> {
215 bail!("The dummy module does not use consensus items");
221 }
222
223 async fn process_input<'a, 'b, 'c>(
224 &'a self,
225 dbtx: &mut DatabaseTransaction<'c>,
226 input: &'b DummyInput,
227 ) -> Result<InputMeta, DummyInputError> {
228 let current_funds = dbtx
229 .get_value(&DummyFundsKeyV1(input.account))
230 .await
231 .unwrap_or(Amount::ZERO);
232
233 if input.amount > current_funds
235 && fed_public_key() != input.account
236 && broken_fed_public_key() != input.account
237 {
238 return Err(DummyInputError::NotEnoughFunds);
239 }
240
241 let updated_funds = if fed_public_key() == input.account {
243 current_funds + input.amount
244 } else if broken_fed_public_key() == input.account {
245 current_funds
247 } else {
248 current_funds - input.amount
249 };
250
251 dbtx.insert_entry(&DummyFundsKeyV1(input.account), &updated_funds)
252 .await;
253
254 Ok(InputMeta {
255 amount: TransactionItemAmount {
256 amount: input.amount,
257 fee: self.cfg.consensus.tx_fee,
258 },
259 pub_key: input.account,
261 })
262 }
263
264 async fn process_output<'a, 'b>(
265 &'a self,
266 dbtx: &mut DatabaseTransaction<'b>,
267 output: &'a DummyOutput,
268 out_point: OutPoint,
269 ) -> Result<TransactionItemAmount, DummyOutputError> {
270 let current_funds = dbtx.get_value(&DummyFundsKeyV1(output.account)).await;
272 let updated_funds = current_funds.unwrap_or(Amount::ZERO) + output.amount;
273 dbtx.insert_entry(&DummyFundsKeyV1(output.account), &updated_funds)
274 .await;
275
276 let outcome = DummyOutputOutcome(updated_funds, output.account);
278 dbtx.insert_entry(&DummyOutcomeKey(out_point), &outcome)
279 .await;
280
281 Ok(TransactionItemAmount {
282 amount: output.amount,
283 fee: self.cfg.consensus.tx_fee,
284 })
285 }
286
287 async fn output_status(
288 &self,
289 dbtx: &mut DatabaseTransaction<'_>,
290 out_point: OutPoint,
291 ) -> Option<DummyOutputOutcome> {
292 dbtx.get_value(&DummyOutcomeKey(out_point)).await
294 }
295
296 async fn audit(
297 &self,
298 dbtx: &mut DatabaseTransaction<'_>,
299 audit: &mut Audit,
300 module_instance_id: ModuleInstanceId,
301 ) {
302 audit
303 .add_items(
304 dbtx,
305 module_instance_id,
306 &DummyFundsPrefixV1,
307 |k, v| match k {
308 DummyFundsKeyV1(key)
311 if key == fed_public_key() || key == broken_fed_public_key() =>
312 {
313 v.msats as i64
314 }
315 DummyFundsKeyV1(_) => -(v.msats as i64),
317 },
318 )
319 .await;
320 }
321
322 fn api_endpoints(&self) -> Vec<ApiEndpoint<Self>> {
323 Vec::new()
324 }
325}
326
327impl Dummy {
328 pub fn new(cfg: DummyConfig) -> Dummy {
330 Dummy { cfg }
331 }
332}