use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use std::{env, fs};
use fedimint_bitcoind::{create_bitcoind, DynBitcoindRpc};
use fedimint_client::module::init::{
ClientModuleInitRegistry, DynClientModuleInit, IClientModuleInit,
};
use fedimint_core::bitcoinrpc::BitcoinRpcConfig;
use fedimint_core::config::{
ModuleInitParams, ServerModuleConfigGenParamsRegistry, ServerModuleInitRegistry,
};
use fedimint_core::core::{ModuleInstanceId, ModuleKind};
use fedimint_core::module::{DynServerModuleInit, IServerModuleInit};
use fedimint_core::task::{MaybeSend, MaybeSync, TaskGroup};
use fedimint_core::util::SafeUrl;
use fedimint_logging::{TracingSetup, LOG_TEST};
use tempfile::TempDir;
use tracing::info;
use crate::btc::mock::FakeBitcoinFactory;
use crate::btc::real::RealBitcoinTest;
use crate::btc::BitcoinTest;
use crate::federation::FederationTest;
use crate::gateway::GatewayTest;
use crate::ln::mock::FakeLightningTest;
use crate::ln::real::{ClnLightningTest, LdkLightningTest, LndLightningTest};
use crate::ln::LightningTest;
pub const TIMEOUT: Duration = Duration::from_secs(10);
pub struct Fixtures {
num_peers: u16,
clients: Vec<DynClientModuleInit>,
servers: Vec<DynServerModuleInit>,
params: ServerModuleConfigGenParamsRegistry,
primary_client: ModuleInstanceId,
bitcoin_rpc: BitcoinRpcConfig,
bitcoin: Arc<dyn BitcoinTest>,
dyn_bitcoin_rpc: DynBitcoindRpc,
id: ModuleInstanceId,
}
impl Fixtures {
pub fn new_primary(
client: impl IClientModuleInit + MaybeSend + MaybeSync + 'static,
server: impl IServerModuleInit + MaybeSend + MaybeSync + 'static,
params: impl ModuleInitParams,
) -> Self {
let _ = TracingSetup::default().init();
let real_testing = Fixtures::is_real_test();
let num_peers = 4;
let task_group = TaskGroup::new();
let (dyn_bitcoin_rpc, bitcoin, config): (
DynBitcoindRpc,
Arc<dyn BitcoinTest>,
BitcoinRpcConfig,
) = if real_testing {
let rpc_config = BitcoinRpcConfig::from_env_vars().unwrap();
let dyn_bitcoin_rpc = create_bitcoind(&rpc_config, task_group.make_handle()).unwrap();
let bitcoincore_url = env::var("FM_TEST_BITCOIND_RPC")
.expect("Must have bitcoind RPC defined for real tests")
.parse()
.expect("Invalid bitcoind RPC URL");
let bitcoin = RealBitcoinTest::new(&bitcoincore_url, dyn_bitcoin_rpc.clone());
(dyn_bitcoin_rpc, Arc::new(bitcoin), rpc_config)
} else {
let FakeBitcoinFactory { bitcoin, config } = FakeBitcoinFactory::register_new();
let dyn_bitcoin_rpc = DynBitcoindRpc::from(bitcoin.clone());
let bitcoin = Arc::new(bitcoin);
(dyn_bitcoin_rpc, bitcoin, config)
};
Self {
num_peers,
clients: vec![],
servers: vec![],
params: Default::default(),
primary_client: 0,
bitcoin_rpc: config,
bitcoin,
dyn_bitcoin_rpc,
id: 0,
}
.with_module(client, server, params)
}
pub fn is_real_test() -> bool {
env::var("FM_TEST_USE_REAL_DAEMONS") == Ok("1".to_string())
}
pub fn with_module(
mut self,
client: impl IClientModuleInit + MaybeSend + MaybeSync + 'static,
server: impl IServerModuleInit + MaybeSend + MaybeSync + 'static,
params: impl ModuleInitParams,
) -> Self {
self.params
.attach_config_gen_params(self.id, server.module_kind(), params);
self.clients.push(DynClientModuleInit::from(client));
self.servers.push(DynServerModuleInit::from(server));
self.id += 1;
self
}
pub async fn new_fed(&self) -> FederationTest {
self.new_fed_with_peers(self.num_peers).await
}
pub async fn new_fed_with_peers(&self, num_peers: u16) -> FederationTest {
info!(target: LOG_TEST, num_peers, "Setting federation with peers");
FederationTest::new(
num_peers,
tokio::task::block_in_place(|| fedimint_portalloc::port_alloc(num_peers * 2))
.expect("Failed to allocate a port range"),
self.params.clone(),
ServerModuleInitRegistry::from(self.servers.clone()),
ClientModuleInitRegistry::from(self.clients.clone()),
self.primary_client,
)
.await
}
pub async fn new_gateway(
&self,
ln: Box<dyn LightningTest>,
num_route_hints: u32,
cli_password: Option<String>,
) -> GatewayTest {
let server_gens = ServerModuleInitRegistry::from(self.servers.clone());
let module_kinds = self.params.iter_modules().map(|(id, kind, _)| (id, kind));
let decoders = server_gens.available_decoders(module_kinds).unwrap();
let clients = self.clients.clone().into_iter();
GatewayTest::new(
tokio::task::block_in_place(|| fedimint_portalloc::port_alloc(1))
.expect("Failed to allocate a port range"),
cli_password,
ln,
decoders,
ClientModuleInitRegistry::from_iter(clients.filter(|client| {
client.to_dyn_common().module_kind() != ModuleKind::from_static_str("ln")
})),
num_route_hints,
)
.await
}
pub async fn lnd(&self) -> Box<dyn LightningTest> {
match Fixtures::is_real_test() {
true => Box::new(LndLightningTest::new().await),
false => Box::new(FakeLightningTest::new()),
}
}
pub async fn cln(&self) -> Box<dyn LightningTest> {
match Fixtures::is_real_test() {
true => Box::new(ClnLightningTest::new().await),
false => Box::new(FakeLightningTest::new()),
}
}
pub async fn spawn_ldk(bitcoin: Arc<dyn BitcoinTest>) -> LdkLightningTest {
let db_dir = test_dir(&format!("LDKNode-{}", rand::random::<u64>())).0;
LdkLightningTest::new(db_dir, bitcoin.clone())
.await
.expect("Error spawning LDK Node")
}
pub fn bitcoin_server(&self) -> BitcoinRpcConfig {
self.bitcoin_rpc.clone()
}
pub fn bitcoin_client(&self) -> BitcoinRpcConfig {
match Fixtures::is_real_test() {
true => BitcoinRpcConfig {
kind: "esplora".to_string(),
url: SafeUrl::parse(&format!(
"http://127.0.0.1:{}/",
env::var("FM_PORT_ESPLORA").unwrap_or(String::from("50002"))
))
.expect("Failed to parse default esplora server"),
},
false => self.bitcoin_rpc.clone(),
}
}
pub fn bitcoin(&self) -> Arc<dyn BitcoinTest> {
self.bitcoin.clone()
}
pub fn dyn_bitcoin_rpc(&self) -> DynBitcoindRpc {
self.dyn_bitcoin_rpc.clone()
}
}
pub fn test_dir(pathname: &str) -> (PathBuf, Option<TempDir>) {
let (parent, maybe_tmp_dir_guard) = match env::var("FM_TEST_DIR") {
Ok(directory) => (directory, None),
Err(_) => {
let random = format!("test-{}", rand::random::<u64>());
let guard = tempfile::Builder::new().prefix(&random).tempdir().unwrap();
let directory = guard.path().to_str().unwrap().to_owned();
(directory, Some(guard))
}
};
let fullpath = PathBuf::from(parent).join(pathname);
fs::create_dir_all(fullpath.clone()).expect("Can make dirs");
(fullpath, maybe_tmp_dir_guard)
}