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 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, 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, migrate_to_v2,
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 _disable_base_fees: bool,
126 ) -> BTreeMap<PeerId, ServerModuleConfig> {
127 let params = self.parse_params(params).unwrap();
128 peers
130 .iter()
131 .map(|&peer| {
132 let config = DummyConfig {
133 private: DummyConfigPrivate,
134 consensus: DummyConfigConsensus {
135 tx_fee: params.consensus.tx_fee,
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 params: &ConfigGenModuleParams,
148 _disable_base_fees: bool,
149 ) -> anyhow::Result<ServerModuleConfig> {
150 let params = self.parse_params(params).unwrap();
151
152 Ok(DummyConfig {
153 private: DummyConfigPrivate,
154 consensus: DummyConfigConsensus {
155 tx_fee: params.consensus.tx_fee,
156 },
157 }
158 .to_erased())
159 }
160
161 fn get_client_config(
163 &self,
164 config: &ServerModuleConsensusConfig,
165 ) -> anyhow::Result<DummyClientConfig> {
166 let config = DummyConfigConsensus::from_erased(config)?;
167 Ok(DummyClientConfig {
168 tx_fee: config.tx_fee,
169 })
170 }
171
172 fn validate_config(
173 &self,
174 _identity: &PeerId,
175 _config: ServerModuleConfig,
176 ) -> anyhow::Result<()> {
177 Ok(())
178 }
179
180 fn get_database_migrations(
182 &self,
183 ) -> BTreeMap<DatabaseVersion, ServerModuleDbMigrationFn<Dummy>> {
184 let mut migrations: BTreeMap<DatabaseVersion, ServerModuleDbMigrationFn<Dummy>> =
185 BTreeMap::new();
186 migrations.insert(
187 DatabaseVersion(0),
188 Box::new(|ctx| migrate_to_v1(ctx).boxed()),
189 );
190 migrations.insert(
191 DatabaseVersion(1),
192 Box::new(|ctx| migrate_to_v2(ctx).boxed()),
193 );
194 migrations
195 }
196}
197
198#[derive(Debug)]
200pub struct Dummy {
201 pub cfg: DummyConfig,
202}
203
204#[async_trait]
206impl ServerModule for Dummy {
207 type Common = DummyModuleTypes;
209 type Init = DummyInit;
210
211 async fn consensus_proposal(
212 &self,
213 _dbtx: &mut DatabaseTransaction<'_>,
214 ) -> Vec<DummyConsensusItem> {
215 Vec::new()
216 }
217
218 async fn process_consensus_item<'a, 'b>(
219 &'a self,
220 _dbtx: &mut DatabaseTransaction<'b>,
221 _consensus_item: DummyConsensusItem,
222 _peer_id: PeerId,
223 ) -> anyhow::Result<()> {
224 bail!("The dummy module does not use consensus items");
230 }
231
232 async fn process_input<'a, 'b, 'c>(
233 &'a self,
234 dbtx: &mut DatabaseTransaction<'c>,
235 input: &'b DummyInput,
236 _in_point: InPoint,
237 ) -> Result<InputMeta, DummyInputError> {
238 let DummyInput::V0(input) = input else {
239 return Err(DummyInputError::InvalidVersion);
240 };
241 let current_funds = dbtx
242 .get_value(&DummyFundsKeyV1(input.account))
243 .await
244 .unwrap_or(Amount::ZERO);
245
246 if input.amount > current_funds
248 && fed_public_key() != input.account
249 && broken_fed_public_key() != input.account
250 {
251 return Err(DummyInputError::NotEnoughFunds);
252 }
253
254 let updated_funds = if fed_public_key() == input.account {
256 current_funds + input.amount
257 } else if broken_fed_public_key() == input.account {
258 current_funds
260 } else {
261 current_funds.saturating_sub(input.amount)
262 };
263
264 dbtx.insert_entry(&DummyFundsKeyV1(input.account), &updated_funds)
265 .await;
266
267 Ok(InputMeta {
268 amount: TransactionItemAmounts {
269 amounts: Amounts::new_bitcoin(input.amount),
270 fees: Amounts::new_bitcoin(self.cfg.consensus.tx_fee),
271 },
272 pub_key: input.account,
274 })
275 }
276
277 async fn process_output<'a, 'b>(
278 &'a self,
279 dbtx: &mut DatabaseTransaction<'b>,
280 output: &'a DummyOutput,
281 out_point: OutPoint,
282 ) -> Result<TransactionItemAmounts, DummyOutputError> {
283 let DummyOutput::V0(output) = output else {
284 return Err(DummyOutputError::InvalidVersion);
285 };
286 let current_funds = dbtx.get_value(&DummyFundsKeyV1(output.account)).await;
288 let updated_funds = current_funds.unwrap_or(Amount::ZERO) + output.amount;
289 dbtx.insert_entry(&DummyFundsKeyV1(output.account), &updated_funds)
290 .await;
291
292 let outcome = DummyOutputOutcome(updated_funds, output.unit, output.account);
294 dbtx.insert_entry(&DummyOutcomeKey(out_point), &outcome)
295 .await;
296
297 Ok(TransactionItemAmounts {
298 amounts: Amounts::new_bitcoin(output.amount),
299 fees: Amounts::new_bitcoin(self.cfg.consensus.tx_fee),
300 })
301 }
302
303 async fn output_status(
304 &self,
305 dbtx: &mut DatabaseTransaction<'_>,
306 out_point: OutPoint,
307 ) -> Option<DummyOutputOutcome> {
308 dbtx.get_value(&DummyOutcomeKey(out_point)).await
310 }
311
312 async fn audit(
313 &self,
314 dbtx: &mut DatabaseTransaction<'_>,
315 audit: &mut Audit,
316 module_instance_id: ModuleInstanceId,
317 ) {
318 audit
319 .add_items(
320 dbtx,
321 module_instance_id,
322 &DummyFundsPrefixV1,
323 |k, v| match k {
324 DummyFundsKeyV1(key)
327 if key == fed_public_key() || key == broken_fed_public_key() =>
328 {
329 v.msats as i64
330 }
331 DummyFundsKeyV1(_) => -(v.msats as i64),
333 },
334 )
335 .await;
336 }
337
338 fn api_endpoints(&self) -> Vec<ApiEndpoint<Self>> {
339 Vec::new()
340 }
341}
342
343impl Dummy {
344 pub fn new(cfg: DummyConfig) -> Dummy {
346 Dummy { cfg }
347 }
348}