#![deny(clippy::pedantic)]
#![allow(clippy::cast_possible_wrap)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::must_use_candidate)]
use std::collections::BTreeMap;
use anyhow::bail;
use async_trait::async_trait;
use fedimint_core::config::{
ServerModuleConfig, ServerModuleConsensusConfig, TypedServerModuleConfig,
TypedServerModuleConsensusConfig,
};
use fedimint_core::core::ModuleInstanceId;
use fedimint_core::db::{DatabaseTransaction, DatabaseVersion, IDatabaseTransactionOpsCoreTyped};
use fedimint_core::module::audit::Audit;
use fedimint_core::module::{
Amounts, ApiEndpoint, CORE_CONSENSUS_VERSION, CoreConsensusVersion, InputMeta,
ModuleConsensusVersion, ModuleInit, SupportedModuleApiVersions, TransactionItemAmounts,
};
use fedimint_core::{Amount, InPoint, OutPoint, PeerId, push_db_pair_items};
use fedimint_dummy_common::config::{
DummyClientConfig, DummyConfig, DummyConfigConsensus, DummyConfigPrivate,
};
use fedimint_dummy_common::{
DummyCommonInit, DummyConsensusItem, DummyInput, DummyInputError, DummyModuleTypes,
DummyOutput, DummyOutputError, DummyOutputOutcome, MODULE_CONSENSUS_VERSION,
broken_fed_public_key, fed_public_key,
};
use fedimint_server_core::config::PeerHandleOps;
use fedimint_server_core::migration::ServerModuleDbMigrationFn;
use fedimint_server_core::{
ConfigGenModuleArgs, ServerModule, ServerModuleInit, ServerModuleInitArgs,
};
use futures::{FutureExt, StreamExt};
use strum::IntoEnumIterator;
use crate::db::{
DbKeyPrefix, DummyFundsKeyV1, DummyFundsPrefixV1, DummyOutcomeKey, DummyOutcomePrefix,
migrate_to_v1, migrate_to_v2,
};
pub mod db;
#[derive(Debug, Clone)]
pub struct DummyInit;
impl ModuleInit for DummyInit {
type Common = DummyCommonInit;
async fn dump_database(
&self,
dbtx: &mut DatabaseTransaction<'_>,
prefix_names: Vec<String>,
) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_> {
let mut items: BTreeMap<String, Box<dyn erased_serde::Serialize + Send>> = BTreeMap::new();
let filtered_prefixes = DbKeyPrefix::iter().filter(|f| {
prefix_names.is_empty() || prefix_names.contains(&f.to_string().to_lowercase())
});
for table in filtered_prefixes {
match table {
DbKeyPrefix::Funds => {
push_db_pair_items!(
dbtx,
DummyFundsPrefixV1,
DummyFundsKeyV1,
Amount,
items,
"Dummy Funds"
);
}
DbKeyPrefix::Outcome => {
push_db_pair_items!(
dbtx,
DummyOutcomePrefix,
DummyOutcomeKey,
DummyOutputOutcome,
items,
"Dummy Outputs"
);
}
}
}
Box::new(items.into_iter())
}
}
#[async_trait]
impl ServerModuleInit for DummyInit {
type Module = Dummy;
fn versions(&self, _core: CoreConsensusVersion) -> &[ModuleConsensusVersion] {
&[MODULE_CONSENSUS_VERSION]
}
fn supported_api_versions(&self) -> SupportedModuleApiVersions {
SupportedModuleApiVersions::from_raw(
(CORE_CONSENSUS_VERSION.major, CORE_CONSENSUS_VERSION.minor),
(
MODULE_CONSENSUS_VERSION.major,
MODULE_CONSENSUS_VERSION.minor,
),
&[(0, 0)],
)
}
async fn init(&self, args: &ServerModuleInitArgs<Self>) -> anyhow::Result<Self::Module> {
Ok(Dummy::new(args.cfg().to_typed()?))
}
fn trusted_dealer_gen(
&self,
peers: &[PeerId],
_args: &ConfigGenModuleArgs,
) -> BTreeMap<PeerId, ServerModuleConfig> {
peers
.iter()
.map(|&peer| {
let config = DummyConfig {
private: DummyConfigPrivate,
consensus: DummyConfigConsensus {
tx_fee: Amount::ZERO,
},
};
(peer, config.to_erased())
})
.collect()
}
async fn distributed_gen(
&self,
_peers: &(dyn PeerHandleOps + Send + Sync),
_args: &ConfigGenModuleArgs,
) -> anyhow::Result<ServerModuleConfig> {
Ok(DummyConfig {
private: DummyConfigPrivate,
consensus: DummyConfigConsensus {
tx_fee: Amount::ZERO,
},
}
.to_erased())
}
fn get_client_config(
&self,
config: &ServerModuleConsensusConfig,
) -> anyhow::Result<DummyClientConfig> {
let config = DummyConfigConsensus::from_erased(config)?;
Ok(DummyClientConfig {
tx_fee: config.tx_fee,
})
}
fn validate_config(
&self,
_identity: &PeerId,
_config: ServerModuleConfig,
) -> anyhow::Result<()> {
Ok(())
}
fn get_database_migrations(
&self,
) -> BTreeMap<DatabaseVersion, ServerModuleDbMigrationFn<Dummy>> {
let mut migrations: BTreeMap<DatabaseVersion, ServerModuleDbMigrationFn<Dummy>> =
BTreeMap::new();
migrations.insert(
DatabaseVersion(0),
Box::new(|ctx| migrate_to_v1(ctx).boxed()),
);
migrations.insert(
DatabaseVersion(1),
Box::new(|ctx| migrate_to_v2(ctx).boxed()),
);
migrations
}
}
#[derive(Debug)]
pub struct Dummy {
pub cfg: DummyConfig,
}
#[async_trait]
impl ServerModule for Dummy {
type Common = DummyModuleTypes;
type Init = DummyInit;
async fn consensus_proposal(
&self,
_dbtx: &mut DatabaseTransaction<'_>,
) -> Vec<DummyConsensusItem> {
Vec::new()
}
async fn process_consensus_item<'a, 'b>(
&'a self,
_dbtx: &mut DatabaseTransaction<'b>,
_consensus_item: DummyConsensusItem,
_peer_id: PeerId,
) -> anyhow::Result<()> {
bail!("The dummy module does not use consensus items");
}
async fn process_input<'a, 'b, 'c>(
&'a self,
dbtx: &mut DatabaseTransaction<'c>,
input: &'b DummyInput,
_in_point: InPoint,
) -> Result<InputMeta, DummyInputError> {
let DummyInput::V0(input) = input else {
return Err(DummyInputError::InvalidVersion);
};
let current_funds = dbtx
.get_value(&DummyFundsKeyV1(input.account))
.await
.unwrap_or(Amount::ZERO);
if input.amount > current_funds
&& fed_public_key() != input.account
&& broken_fed_public_key() != input.account
{
return Err(DummyInputError::NotEnoughFunds);
}
let updated_funds = if fed_public_key() == input.account {
current_funds + input.amount
} else if broken_fed_public_key() == input.account {
current_funds
} else {
current_funds.saturating_sub(input.amount)
};
dbtx.insert_entry(&DummyFundsKeyV1(input.account), &updated_funds)
.await;
Ok(InputMeta {
amount: TransactionItemAmounts {
amounts: Amounts::new_bitcoin(input.amount),
fees: Amounts::new_bitcoin(self.cfg.consensus.tx_fee),
},
pub_key: input.account,
})
}
async fn process_output<'a, 'b>(
&'a self,
dbtx: &mut DatabaseTransaction<'b>,
output: &'a DummyOutput,
out_point: OutPoint,
) -> Result<TransactionItemAmounts, DummyOutputError> {
let DummyOutput::V0(output) = output else {
return Err(DummyOutputError::InvalidVersion);
};
let current_funds = dbtx.get_value(&DummyFundsKeyV1(output.account)).await;
let updated_funds = current_funds.unwrap_or(Amount::ZERO) + output.amount;
dbtx.insert_entry(&DummyFundsKeyV1(output.account), &updated_funds)
.await;
let outcome = DummyOutputOutcome(updated_funds, output.unit, output.account);
dbtx.insert_entry(&DummyOutcomeKey(out_point), &outcome)
.await;
Ok(TransactionItemAmounts {
amounts: Amounts::new_bitcoin(output.amount),
fees: Amounts::new_bitcoin(self.cfg.consensus.tx_fee),
})
}
async fn output_status(
&self,
dbtx: &mut DatabaseTransaction<'_>,
out_point: OutPoint,
) -> Option<DummyOutputOutcome> {
dbtx.get_value(&DummyOutcomeKey(out_point)).await
}
async fn audit(
&self,
dbtx: &mut DatabaseTransaction<'_>,
audit: &mut Audit,
module_instance_id: ModuleInstanceId,
) {
audit
.add_items(
dbtx,
module_instance_id,
&DummyFundsPrefixV1,
|k, v| match k {
DummyFundsKeyV1(key)
if key == fed_public_key() || key == broken_fed_public_key() =>
{
v.msats as i64
}
DummyFundsKeyV1(_) => -(v.msats as i64),
},
)
.await;
}
fn api_endpoints(&self) -> Vec<ApiEndpoint<Self>> {
Vec::new()
}
}
impl Dummy {
pub fn new(cfg: DummyConfig) -> Dummy {
Dummy { cfg }
}
}