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::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_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" => BitcoindClient::new(&rpc_config.url).unwrap().into_dyn(),
90 "esplora" => EsploraClient::new(&rpc_config.url).unwrap().into_dyn(),
91 kind => panic!("Unknown bitcoin rpc kind {kind}"),
92 };
93
94 let bitcoincore_url = env::var(FM_TEST_BITCOIND_RPC_ENV)
95 .expect("Must have bitcoind RPC defined for real tests")
96 .parse()
97 .expect("Invalid bitcoind RPC URL");
98 let bitcoin = RealBitcoinTest::new(&bitcoincore_url, server_bitcoin_rpc.clone());
99
100 (Arc::new(bitcoin), rpc_config, server_bitcoin_rpc, None)
101 } else {
102 let bitcoin = FakeBitcoinTest::new();
103
104 let config = BitcoinRpcConfig {
105 kind: format!("test_btc-{}", rand::random::<u64>()),
106 url: "http://ignored".parse().unwrap(),
107 };
108
109 let dyn_bitcoin_rpc = IBitcoindRpc::into_dyn(bitcoin.clone());
110
111 let server_bitcoin_rpc = IServerBitcoinRpc::into_dyn(bitcoin.clone());
112
113 let bitcoin = Arc::new(bitcoin);
114
115 (
116 bitcoin.clone(),
117 config,
118 server_bitcoin_rpc,
119 Some(dyn_bitcoin_rpc),
120 )
121 };
122
123 Self {
124 clients: vec![],
125 servers: vec![],
126 params: ModuleRegistry::default(),
127 bitcoin_rpc: config,
128 fake_bitcoin_rpc,
129 bitcoin,
130 server_bitcoin_rpc: bitcoin_rpc_connection,
131 primary_module_kind: IClientModuleInit::module_kind(&client),
132 id: 0,
133 }
134 .with_module(client, server, params)
135 }
136
137 pub fn is_real_test() -> bool {
138 env::var(FM_TEST_USE_REAL_DAEMONS_ENV) == Ok("1".to_string())
139 }
140
141 pub fn with_module(
144 mut self,
145 client: impl IClientModuleInit + 'static,
146 server: impl IServerModuleInit + MaybeSend + MaybeSync + 'static,
147 params: impl ModuleInitParams,
148 ) -> Self {
149 self.params
150 .attach_config_gen_params_by_id(self.id, server.module_kind(), params);
151 self.clients.push(DynClientModuleInit::from(client));
152 self.servers.push(DynServerModuleInit::from(server));
153 self.id += 1;
154
155 self
156 }
157
158 pub fn with_server_only_module(
159 mut self,
160 server: impl IServerModuleInit + MaybeSend + MaybeSync + 'static,
161 params: impl ModuleInitParams,
162 ) -> Self {
163 self.params
164 .attach_config_gen_params_by_id(self.id, server.module_kind(), params);
165 self.servers.push(DynServerModuleInit::from(server));
166 self.id += 1;
167
168 self
169 }
170
171 pub async fn new_fed_degraded(&self) -> FederationTest {
173 self.new_fed_builder(1).build().await
174 }
175
176 pub async fn new_fed_not_degraded(&self) -> FederationTest {
178 self.new_fed_builder(0).build().await
179 }
180
181 pub fn new_fed_builder(&self, num_offline: u16) -> FederationTestBuilder {
184 FederationTestBuilder::new(
185 self.params.clone(),
186 ServerModuleInitRegistry::from(self.servers.clone()),
187 ClientModuleInitRegistry::from(self.clients.clone()),
188 self.primary_module_kind.clone(),
189 num_offline,
190 self.server_bitcoin_rpc(),
191 )
192 }
193
194 pub async fn new_gateway(&self, lightning_module_mode: LightningModuleMode) -> Gateway {
196 let server_gens = ServerModuleInitRegistry::from(self.servers.clone());
197 let module_kinds = self.params.iter_modules().map(|(id, kind, _)| (id, kind));
198 let decoders = server_gens.available_decoders(module_kinds).unwrap();
199 let gateway_db = Database::new(MemDatabase::new(), decoders.clone());
200 let clients = self.clients.clone().into_iter();
201
202 let registry = clients
203 .filter(|client| {
204 client.to_dyn_common().module_kind() != ModuleKind::from_static_str("ln")
206 })
207 .filter(|client| {
208 client.to_dyn_common().module_kind() != ModuleKind::from_static_str("lnv2")
210 })
211 .collect();
212
213 let (path, _config_dir) = test_dir(&format!("gateway-{}", rand::random::<u64>()));
214
215 let client_builder: GatewayClientBuilder =
217 GatewayClientBuilder::new(path.clone(), registry, ModuleKind::from_static_str("dummy"));
218
219 let ln_client: Arc<dyn ILnRpcClient> = Arc::new(FakeLightningTest::new());
220
221 let (lightning_public_key, lightning_alias, lightning_network, _, _) = ln_client
222 .parsed_node_info()
223 .await
224 .expect("Could not get Lightning info");
225 let lightning_context = LightningContext {
226 lnrpc: ln_client.clone(),
227 lightning_public_key,
228 lightning_alias,
229 lightning_network,
230 };
231
232 let listen: SocketAddr = "127.0.0.1:9000".parse().unwrap();
234 let address: SafeUrl = format!("http://{listen}").parse().unwrap();
235
236 Gateway::new_with_custom_registry(
237 LightningMode::Lnd {
240 lnd_rpc_addr: "FakeRpcAddr".to_string(),
241 lnd_tls_cert: "FakeTlsCert".to_string(),
242 lnd_macaroon: "FakeMacaroon".to_string(),
243 },
244 client_builder,
245 listen,
246 address.clone(),
247 bcrypt::HashParts::from_str(
248 &bcrypt::hash(DEFAULT_GATEWAY_PASSWORD, bcrypt::DEFAULT_COST).unwrap(),
249 )
250 .unwrap(),
251 bitcoin::Network::Regtest,
252 0,
253 gateway_db,
254 fedimint_gateway_server::GatewayState::Running { lightning_context },
258 lightning_module_mode,
259 )
260 .await
261 .expect("Failed to create gateway")
262 }
263
264 pub fn bitcoin_server(&self) -> BitcoinRpcConfig {
266 self.bitcoin_rpc.clone()
267 }
268
269 pub fn client_esplora_rpc(&self) -> DynBitcoindRpc {
270 if Fixtures::is_real_test() {
271 create_esplora_rpc(
272 &SafeUrl::parse(&format!(
273 "http://127.0.0.1:{}/",
274 env::var(FM_PORT_ESPLORA_ENV).unwrap_or(String::from("50002"))
275 ))
276 .expect("Failed to parse default esplora server"),
277 )
278 .unwrap()
279 } else {
280 self.fake_bitcoin_rpc.clone().unwrap()
281 }
282 }
283
284 pub fn bitcoin(&self) -> Arc<dyn BitcoinTest> {
286 self.bitcoin.clone()
287 }
288
289 pub fn server_bitcoin_rpc(&self) -> DynServerBitcoinRpc {
290 self.server_bitcoin_rpc.clone()
291 }
292}