1use std::env;
2use std::net::SocketAddr;
3use std::str::FromStr;
4use std::sync::Arc;
5use std::time::Duration;
6
7use fedimint_bitcoind::{DynBitcoindRpc, create_bitcoind};
8use fedimint_client::module_init::{
9 ClientModuleInitRegistry, DynClientModuleInit, IClientModuleInit,
10};
11use fedimint_core::config::{ModuleInitParams, ServerModuleConfigGenParamsRegistry};
12use fedimint_core::core::{ModuleInstanceId, ModuleKind};
13use fedimint_core::db::Database;
14use fedimint_core::db::mem_impl::MemDatabase;
15use fedimint_core::envs::BitcoinRpcConfig;
16use fedimint_core::module::registry::ModuleRegistry;
17use fedimint_core::task::{MaybeSend, MaybeSync};
18use fedimint_core::util::SafeUrl;
19use fedimint_gateway_common::LightningMode;
20use fedimint_gateway_server::Gateway;
21use fedimint_gateway_server::client::GatewayClientBuilder;
22use fedimint_gateway_server::config::LightningModuleMode;
23use fedimint_lightning::{ILnRpcClient, LightningContext};
24use fedimint_logging::TracingSetup;
25use fedimint_server::core::{DynServerModuleInit, IServerModuleInit, ServerModuleInitRegistry};
26use fedimint_testing_core::test_dir;
27
28use crate::btc::BitcoinTest;
29use crate::btc::mock::FakeBitcoinFactory;
30use crate::btc::real::RealBitcoinTest;
31use crate::envs::{
32 FM_PORT_ESPLORA_ENV, FM_TEST_BACKEND_BITCOIN_RPC_KIND_ENV, FM_TEST_BACKEND_BITCOIN_RPC_URL_ENV,
33 FM_TEST_BITCOIND_RPC_ENV, FM_TEST_USE_REAL_DAEMONS_ENV,
34};
35use crate::federation::{FederationTest, FederationTestBuilder};
36use crate::ln::FakeLightningTest;
37
38pub const TIMEOUT: Duration = Duration::from_secs(10);
40
41pub const DEFAULT_GATEWAY_PASSWORD: &str = "thereisnosecondbest";
42
43pub struct Fixtures {
45 clients: Vec<DynClientModuleInit>,
46 servers: Vec<DynServerModuleInit>,
47 params: ServerModuleConfigGenParamsRegistry,
48 bitcoin_rpc: BitcoinRpcConfig,
49 bitcoin: Arc<dyn BitcoinTest>,
50 dyn_bitcoin_rpc: DynBitcoindRpc,
51 primary_module_kind: ModuleKind,
52 id: ModuleInstanceId,
53}
54
55impl Fixtures {
56 pub fn new_primary(
57 client: impl IClientModuleInit + 'static,
58 server: impl IServerModuleInit + MaybeSend + MaybeSync + 'static,
59 params: impl ModuleInitParams,
60 ) -> Self {
61 let _ = TracingSetup::default().init();
63 let real_testing = Fixtures::is_real_test();
64 let (dyn_bitcoin_rpc, bitcoin, config): (
65 DynBitcoindRpc,
66 Arc<dyn BitcoinTest>,
67 BitcoinRpcConfig,
68 ) = if real_testing {
69 let override_bitcoin_rpc_kind = env::var(FM_TEST_BACKEND_BITCOIN_RPC_KIND_ENV);
72 let override_bitcoin_rpc_url = env::var(FM_TEST_BACKEND_BITCOIN_RPC_URL_ENV);
73
74 let rpc_config = match (override_bitcoin_rpc_kind, override_bitcoin_rpc_url) {
75 (Ok(kind), Ok(url)) => BitcoinRpcConfig {
76 kind: kind.parse().expect("must provide valid kind"),
77 url: url.parse().expect("must provide valid url"),
78 },
79 _ => BitcoinRpcConfig::get_defaults_from_env_vars()
80 .expect("must provide valid default env vars"),
81 };
82
83 let dyn_bitcoin_rpc = create_bitcoind(&rpc_config).unwrap();
84 let bitcoincore_url = env::var(FM_TEST_BITCOIND_RPC_ENV)
85 .expect("Must have bitcoind RPC defined for real tests")
86 .parse()
87 .expect("Invalid bitcoind RPC URL");
88 let bitcoin = RealBitcoinTest::new(&bitcoincore_url, dyn_bitcoin_rpc.clone());
89
90 (dyn_bitcoin_rpc, Arc::new(bitcoin), rpc_config)
91 } else {
92 let FakeBitcoinFactory { bitcoin, config } = FakeBitcoinFactory::register_new();
93 let dyn_bitcoin_rpc = DynBitcoindRpc::from(bitcoin.clone());
94 let bitcoin = Arc::new(bitcoin);
95 (dyn_bitcoin_rpc, bitcoin, config)
96 };
97
98 Self {
99 clients: vec![],
100 servers: vec![],
101 params: ModuleRegistry::default(),
102 bitcoin_rpc: config,
103 bitcoin,
104 dyn_bitcoin_rpc,
105 primary_module_kind: IClientModuleInit::module_kind(&client),
106 id: 0,
107 }
108 .with_module(client, server, params)
109 }
110
111 pub fn is_real_test() -> bool {
112 env::var(FM_TEST_USE_REAL_DAEMONS_ENV) == Ok("1".to_string())
113 }
114
115 pub fn with_module(
118 mut self,
119 client: impl IClientModuleInit + 'static,
120 server: impl IServerModuleInit + MaybeSend + MaybeSync + 'static,
121 params: impl ModuleInitParams,
122 ) -> Self {
123 self.params
124 .attach_config_gen_params_by_id(self.id, server.module_kind(), params);
125 self.clients.push(DynClientModuleInit::from(client));
126 self.servers.push(DynServerModuleInit::from(server));
127 self.id += 1;
128
129 self
130 }
131
132 pub fn with_server_only_module(
133 mut self,
134 server: impl IServerModuleInit + MaybeSend + MaybeSync + 'static,
135 params: impl ModuleInitParams,
136 ) -> Self {
137 self.params
138 .attach_config_gen_params_by_id(self.id, server.module_kind(), params);
139 self.servers.push(DynServerModuleInit::from(server));
140 self.id += 1;
141
142 self
143 }
144
145 pub async fn new_fed_degraded(&self) -> FederationTest {
147 self.new_fed_builder(1).build().await
148 }
149
150 pub async fn new_fed_not_degraded(&self) -> FederationTest {
152 self.new_fed_builder(0).build().await
153 }
154
155 pub fn new_fed_builder(&self, num_offline: u16) -> FederationTestBuilder {
158 FederationTestBuilder::new(
159 self.params.clone(),
160 ServerModuleInitRegistry::from(self.servers.clone()),
161 ClientModuleInitRegistry::from(self.clients.clone()),
162 self.primary_module_kind.clone(),
163 num_offline,
164 )
165 }
166
167 pub async fn new_gateway(&self, lightning_module_mode: LightningModuleMode) -> Gateway {
169 let server_gens = ServerModuleInitRegistry::from(self.servers.clone());
170 let module_kinds = self.params.iter_modules().map(|(id, kind, _)| (id, kind));
171 let decoders = server_gens.available_decoders(module_kinds).unwrap();
172 let gateway_db = Database::new(MemDatabase::new(), decoders.clone());
173 let clients = self.clients.clone().into_iter();
174
175 let registry = clients
176 .filter(|client| {
177 client.to_dyn_common().module_kind() != ModuleKind::from_static_str("ln")
179 })
180 .filter(|client| {
181 client.to_dyn_common().module_kind() != ModuleKind::from_static_str("lnv2")
183 })
184 .collect();
185
186 let (path, _config_dir) = test_dir(&format!("gateway-{}", rand::random::<u64>()));
187
188 let client_builder: GatewayClientBuilder =
190 GatewayClientBuilder::new(path.clone(), registry, ModuleKind::from_static_str("dummy"));
191
192 let ln_client: Arc<dyn ILnRpcClient> = Arc::new(FakeLightningTest::new());
193
194 let (lightning_public_key, lightning_alias, lightning_network, _, _) = ln_client
195 .parsed_node_info()
196 .await
197 .expect("Could not get Lightning info");
198 let lightning_context = LightningContext {
199 lnrpc: ln_client.clone(),
200 lightning_public_key,
201 lightning_alias,
202 lightning_network,
203 };
204
205 let listen: SocketAddr = "127.0.0.1:9000".parse().unwrap();
207 let address: SafeUrl = format!("http://{listen}").parse().unwrap();
208
209 Gateway::new_with_custom_registry(
210 LightningMode::Lnd {
213 lnd_rpc_addr: "FakeRpcAddr".to_string(),
214 lnd_tls_cert: "FakeTlsCert".to_string(),
215 lnd_macaroon: "FakeMacaroon".to_string(),
216 },
217 client_builder,
218 listen,
219 address.clone(),
220 bcrypt::HashParts::from_str(
221 &bcrypt::hash(DEFAULT_GATEWAY_PASSWORD, bcrypt::DEFAULT_COST).unwrap(),
222 )
223 .unwrap(),
224 bitcoin::Network::Regtest,
225 0,
226 gateway_db,
227 fedimint_gateway_server::GatewayState::Running { lightning_context },
231 lightning_module_mode,
232 )
233 .await
234 .expect("Failed to create gateway")
235 }
236
237 pub fn bitcoin_server(&self) -> BitcoinRpcConfig {
239 self.bitcoin_rpc.clone()
240 }
241
242 pub fn bitcoin_client(&self) -> BitcoinRpcConfig {
246 if Fixtures::is_real_test() {
247 BitcoinRpcConfig {
248 kind: "esplora".to_string(),
249 url: SafeUrl::parse(&format!(
250 "http://127.0.0.1:{}/",
251 env::var(FM_PORT_ESPLORA_ENV).unwrap_or(String::from("50002"))
252 ))
253 .expect("Failed to parse default esplora server"),
254 }
255 } else {
256 self.bitcoin_rpc.clone()
257 }
258 }
259
260 pub fn bitcoin(&self) -> Arc<dyn BitcoinTest> {
262 self.bitcoin.clone()
263 }
264
265 pub fn dyn_bitcoin_rpc(&self) -> DynBitcoindRpc {
266 self.dyn_bitcoin_rpc.clone()
267 }
268}