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