1use std::env;
2use std::net::SocketAddr;
3use std::str::FromStr;
4use std::sync::Arc;
5use std::time::Duration;
6
7use fedimint_bitcoind::{DynBitcoindRpc, IBitcoindRpc, create_esplora_rpc};
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::{ChainSource, LightningMode};
20use fedimint_gateway_server::Gateway;
21use fedimint_gateway_server::client::GatewayClientBuilder;
22use fedimint_gateway_server::config::{DatabaseBackend, LightningModuleMode};
23use fedimint_lightning::{ILnRpcClient, LightningContext};
24use fedimint_logging::TracingSetup;
25use fedimint_server::core::{DynServerModuleInit, IServerModuleInit, ServerModuleInitRegistry};
26use fedimint_server_bitcoin_rpc::bitcoind::BitcoindClient;
27use fedimint_server_bitcoin_rpc::esplora::EsploraClient;
28use fedimint_server_core::bitcoin_rpc::{DynServerBitcoinRpc, IServerBitcoinRpc};
29use fedimint_testing_core::test_dir;
30
31use crate::btc::BitcoinTest;
32use crate::btc::mock::FakeBitcoinTest;
33use crate::btc::real::RealBitcoinTest;
34use crate::envs::{
35 FM_PORT_ESPLORA_ENV, FM_TEST_BACKEND_BITCOIN_RPC_KIND_ENV, FM_TEST_BACKEND_BITCOIN_RPC_URL_ENV,
36 FM_TEST_BITCOIND_RPC_ENV, FM_TEST_USE_REAL_DAEMONS_ENV,
37};
38use crate::federation::{FederationTest, FederationTestBuilder};
39use crate::ln::FakeLightningTest;
40
41pub const TIMEOUT: Duration = Duration::from_secs(10);
43
44pub const DEFAULT_GATEWAY_PASSWORD: &str = "thereisnosecondbest";
45
46pub struct Fixtures {
48 clients: Vec<DynClientModuleInit>,
49 servers: Vec<DynServerModuleInit>,
50 params: ServerModuleConfigGenParamsRegistry,
51 bitcoin_rpc: BitcoinRpcConfig,
52 bitcoin: Arc<dyn BitcoinTest>,
53 fake_bitcoin_rpc: Option<DynBitcoindRpc>,
54 server_bitcoin_rpc: DynServerBitcoinRpc,
55 primary_module_kind: ModuleKind,
56 id: ModuleInstanceId,
57}
58
59impl Fixtures {
60 pub fn new_primary(
61 client: impl IClientModuleInit + 'static,
62 server: impl IServerModuleInit + MaybeSend + MaybeSync + 'static,
63 params: impl ModuleInitParams,
64 ) -> Self {
65 let _ = TracingSetup::default().init();
67 let real_testing = Fixtures::is_real_test();
68 let (bitcoin, config, bitcoin_rpc_connection, fake_bitcoin_rpc): (
69 Arc<dyn BitcoinTest>,
70 BitcoinRpcConfig,
71 DynServerBitcoinRpc,
72 Option<DynBitcoindRpc>,
73 ) = if real_testing {
74 let override_bitcoin_rpc_kind = env::var(FM_TEST_BACKEND_BITCOIN_RPC_KIND_ENV);
77 let override_bitcoin_rpc_url = env::var(FM_TEST_BACKEND_BITCOIN_RPC_URL_ENV);
78
79 let rpc_config = match (override_bitcoin_rpc_kind, override_bitcoin_rpc_url) {
80 (Ok(kind), Ok(url)) => BitcoinRpcConfig {
81 kind: kind.parse().expect("must provide valid kind"),
82 url: url.parse().expect("must provide valid url"),
83 },
84 _ => BitcoinRpcConfig::get_defaults_from_env_vars()
85 .expect("must provide valid default env vars"),
86 };
87
88 let server_bitcoin_rpc = match rpc_config.kind.as_ref() {
89 "bitcoind" => {
90 let bitcoind_username = rpc_config.url.username();
94 let bitcoind_password = rpc_config
95 .url
96 .password()
97 .expect("bitcoind password was not set");
98 BitcoindClient::new(
99 bitcoind_username.to_string(),
100 bitcoind_password.to_string(),
101 &rpc_config.url,
102 )
103 .unwrap()
104 .into_dyn()
105 }
106 "esplora" => EsploraClient::new(&rpc_config.url).unwrap().into_dyn(),
107 kind => panic!("Unknown bitcoin rpc kind {kind}"),
108 };
109
110 let bitcoincore_url = env::var(FM_TEST_BITCOIND_RPC_ENV)
111 .expect("Must have bitcoind RPC defined for real tests")
112 .parse()
113 .expect("Invalid bitcoind RPC URL");
114 let bitcoin = RealBitcoinTest::new(&bitcoincore_url, server_bitcoin_rpc.clone());
115
116 (Arc::new(bitcoin), rpc_config, server_bitcoin_rpc, None)
117 } else {
118 let bitcoin = FakeBitcoinTest::new();
119
120 let config = BitcoinRpcConfig {
121 kind: format!("test_btc-{}", rand::random::<u64>()),
122 url: "http://ignored".parse().unwrap(),
123 };
124
125 let dyn_bitcoin_rpc = IBitcoindRpc::into_dyn(bitcoin.clone());
126
127 let server_bitcoin_rpc = IServerBitcoinRpc::into_dyn(bitcoin.clone());
128
129 let bitcoin = Arc::new(bitcoin);
130
131 (
132 bitcoin.clone(),
133 config,
134 server_bitcoin_rpc,
135 Some(dyn_bitcoin_rpc),
136 )
137 };
138
139 Self {
140 clients: vec![],
141 servers: vec![],
142 params: ModuleRegistry::default(),
143 bitcoin_rpc: config,
144 fake_bitcoin_rpc,
145 bitcoin,
146 server_bitcoin_rpc: bitcoin_rpc_connection,
147 primary_module_kind: IClientModuleInit::module_kind(&client),
148 id: 0,
149 }
150 .with_module(client, server, params)
151 }
152
153 pub fn is_real_test() -> bool {
154 env::var(FM_TEST_USE_REAL_DAEMONS_ENV) == Ok("1".to_string())
155 }
156
157 pub fn with_module(
160 mut self,
161 client: impl IClientModuleInit + 'static,
162 server: impl IServerModuleInit + MaybeSend + MaybeSync + 'static,
163 params: impl ModuleInitParams,
164 ) -> Self {
165 self.params
166 .attach_config_gen_params_by_id(self.id, server.module_kind(), params);
167 self.clients.push(DynClientModuleInit::from(client));
168 self.servers.push(DynServerModuleInit::from(server));
169 self.id += 1;
170
171 self
172 }
173
174 pub fn with_server_only_module(
175 mut self,
176 server: impl IServerModuleInit + MaybeSend + MaybeSync + 'static,
177 params: impl ModuleInitParams,
178 ) -> Self {
179 self.params
180 .attach_config_gen_params_by_id(self.id, server.module_kind(), params);
181 self.servers.push(DynServerModuleInit::from(server));
182 self.id += 1;
183
184 self
185 }
186
187 pub async fn new_fed_degraded(&self) -> FederationTest {
189 self.new_fed_builder(1).build().await
190 }
191
192 pub async fn new_fed_not_degraded(&self) -> FederationTest {
194 self.new_fed_builder(0).build().await
195 }
196
197 pub fn new_fed_builder(&self, num_offline: u16) -> FederationTestBuilder {
200 FederationTestBuilder::new(
201 self.params.clone(),
202 ServerModuleInitRegistry::from(self.servers.clone()),
203 ClientModuleInitRegistry::from(self.clients.clone()),
204 self.primary_module_kind.clone(),
205 num_offline,
206 self.server_bitcoin_rpc(),
207 )
208 }
209
210 pub async fn new_gateway(&self, lightning_module_mode: LightningModuleMode) -> Gateway {
212 let server_gens = ServerModuleInitRegistry::from(self.servers.clone());
213 let module_kinds = self.params.iter_modules().map(|(id, kind, _)| (id, kind));
214 let decoders = server_gens.available_decoders(module_kinds).unwrap();
215 let gateway_db = Database::new(MemDatabase::new(), decoders.clone());
216 let clients = self.clients.clone().into_iter();
217
218 let registry = clients
219 .filter(|client| {
220 client.to_dyn_common().module_kind() != ModuleKind::from_static_str("ln")
222 })
223 .filter(|client| {
224 client.to_dyn_common().module_kind() != ModuleKind::from_static_str("lnv2")
226 })
227 .collect();
228
229 let (path, _config_dir) = test_dir(&format!("gateway-{}", rand::random::<u64>()));
230
231 let client_builder: GatewayClientBuilder = GatewayClientBuilder::new(
233 path.clone(),
234 registry,
235 ModuleKind::from_static_str("dummy"),
236 DatabaseBackend::RocksDb,
237 );
238
239 let ln_client: Arc<dyn ILnRpcClient> = Arc::new(FakeLightningTest::new());
240
241 let (lightning_public_key, lightning_alias, lightning_network, _, _) = ln_client
242 .parsed_node_info()
243 .await
244 .expect("Could not get Lightning info");
245 let lightning_context = LightningContext {
246 lnrpc: ln_client.clone(),
247 lightning_public_key,
248 lightning_alias,
249 lightning_network,
250 };
251
252 let listen: SocketAddr = "127.0.0.1:9000".parse().unwrap();
254 let address: SafeUrl = format!("http://{listen}").parse().unwrap();
255
256 let esplora_server_url = SafeUrl::parse(&format!(
257 "http://127.0.0.1:{}",
258 env::var(FM_PORT_ESPLORA_ENV).unwrap_or(String::from("50002"))
259 ))
260 .expect("Failed to parse default esplora server");
261 let esplora_chain_source = ChainSource::Esplora {
262 server_url: esplora_server_url,
263 };
264
265 Gateway::new_with_custom_registry(
266 LightningMode::Lnd {
269 lnd_rpc_addr: "FakeRpcAddr".to_string(),
270 lnd_tls_cert: "FakeTlsCert".to_string(),
271 lnd_macaroon: "FakeMacaroon".to_string(),
272 },
273 client_builder,
274 listen,
275 address.clone(),
276 bcrypt::HashParts::from_str(
277 &bcrypt::hash(DEFAULT_GATEWAY_PASSWORD, bcrypt::DEFAULT_COST).unwrap(),
278 )
279 .unwrap(),
280 bitcoin::Network::Regtest,
281 0,
282 gateway_db,
283 fedimint_gateway_server::GatewayState::Running { lightning_context },
287 lightning_module_mode,
288 esplora_chain_source,
289 )
290 .await
291 .expect("Failed to create gateway")
292 }
293
294 pub fn bitcoin_server(&self) -> BitcoinRpcConfig {
296 self.bitcoin_rpc.clone()
297 }
298
299 pub fn client_esplora_rpc(&self) -> DynBitcoindRpc {
300 if Fixtures::is_real_test() {
301 create_esplora_rpc(
302 &SafeUrl::parse(&format!(
303 "http://127.0.0.1:{}/",
304 env::var(FM_PORT_ESPLORA_ENV).unwrap_or(String::from("50002"))
305 ))
306 .expect("Failed to parse default esplora server"),
307 )
308 .unwrap()
309 } else {
310 self.fake_bitcoin_rpc.clone().unwrap()
311 }
312 }
313
314 pub fn bitcoin(&self) -> Arc<dyn BitcoinTest> {
316 self.bitcoin.clone()
317 }
318
319 pub fn server_bitcoin_rpc(&self) -> DynServerBitcoinRpc {
320 self.server_bitcoin_rpc.clone()
321 }
322}