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::core::{ModuleInstanceId, ModuleKind};
12use fedimint_core::db::Database;
13use fedimint_core::db::mem_impl::MemDatabase;
14use fedimint_core::envs::BitcoinRpcConfig;
15use fedimint_core::task::{MaybeSend, MaybeSync};
16use fedimint_core::util::SafeUrl;
17use fedimint_gateway_common::{ChainSource, LightningInfo, LightningMode};
18use fedimint_gateway_server::Gateway;
19use fedimint_gateway_server::client::GatewayClientBuilder;
20use fedimint_gateway_server::config::DatabaseBackend;
21use fedimint_lightning::{ILnRpcClient, LightningContext};
22use fedimint_logging::TracingSetup;
23use fedimint_server::core::{DynServerModuleInit, IServerModuleInit, ServerModuleInitRegistry};
24use fedimint_server_bitcoin_rpc::bitcoind::BitcoindClient;
25use fedimint_server_bitcoin_rpc::esplora::EsploraClient;
26use fedimint_server_core::bitcoin_rpc::{DynServerBitcoinRpc, IServerBitcoinRpc};
27use fedimint_testing_core::test_dir;
28
29use crate::btc::BitcoinTest;
30use crate::btc::mock::FakeBitcoinTest;
31use crate::btc::real::RealBitcoinTest;
32use crate::envs::{
33 FM_PORT_ESPLORA_ENV, FM_TEST_BACKEND_BITCOIN_RPC_KIND_ENV, FM_TEST_BACKEND_BITCOIN_RPC_URL_ENV,
34 FM_TEST_BITCOIND_RPC_ENV, FM_TEST_USE_REAL_DAEMONS_ENV,
35};
36use crate::federation::{FederationTest, FederationTestBuilder};
37use crate::ln::FakeLightningTest;
38
39pub const TIMEOUT: Duration = Duration::from_secs(10);
41
42pub const DEFAULT_GATEWAY_PASSWORD: &str = "thereisnosecondbest";
43
44pub struct Fixtures {
46 clients: ClientModuleInitRegistry,
47 servers: ServerModuleInitRegistry,
48 bitcoin_rpc: BitcoinRpcConfig,
49 bitcoin: Arc<dyn BitcoinTest>,
50 fake_bitcoin_rpc: Option<DynBitcoindRpc>,
51 server_bitcoin_rpc: DynServerBitcoinRpc,
52 primary_module_kind: ModuleKind,
53}
54
55impl Fixtures {
56 pub fn new_primary(
57 client: impl IClientModuleInit + 'static,
58 server: impl IServerModuleInit + MaybeSend + MaybeSync + 'static,
59 ) -> Self {
60 let _ = TracingSetup::default().init();
62 let real_testing = Fixtures::is_real_test();
63 let (bitcoin, config, bitcoin_rpc_connection, fake_bitcoin_rpc): (
64 Arc<dyn BitcoinTest>,
65 BitcoinRpcConfig,
66 DynServerBitcoinRpc,
67 Option<DynBitcoindRpc>,
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 server_bitcoin_rpc = match rpc_config.kind.as_ref() {
84 "bitcoind" => {
85 let bitcoind_username = rpc_config.url.username();
89 let bitcoind_password = rpc_config
90 .url
91 .password()
92 .expect("bitcoind password was not set");
93 BitcoindClient::new(
94 bitcoind_username.to_string(),
95 bitcoind_password.to_string(),
96 &rpc_config.url,
97 )
98 .unwrap()
99 .into_dyn()
100 }
101 "esplora" => EsploraClient::new(&rpc_config.url).unwrap().into_dyn(),
102 kind => panic!("Unknown bitcoin rpc kind {kind}"),
103 };
104
105 let bitcoincore_url = env::var(FM_TEST_BITCOIND_RPC_ENV)
106 .expect("Must have bitcoind RPC defined for real tests")
107 .parse()
108 .expect("Invalid bitcoind RPC URL");
109 let bitcoin = RealBitcoinTest::new(&bitcoincore_url, server_bitcoin_rpc.clone());
110
111 (Arc::new(bitcoin), rpc_config, server_bitcoin_rpc, None)
112 } else {
113 let bitcoin = FakeBitcoinTest::new();
114
115 let config = BitcoinRpcConfig {
116 kind: format!("test_btc-{}", rand::random::<u64>()),
117 url: "http://ignored".parse().unwrap(),
118 };
119
120 let dyn_bitcoin_rpc = IBitcoindRpc::into_dyn(bitcoin.clone());
121
122 let server_bitcoin_rpc = IServerBitcoinRpc::into_dyn(bitcoin.clone());
123
124 let bitcoin = Arc::new(bitcoin);
125
126 (
127 bitcoin.clone(),
128 config,
129 server_bitcoin_rpc,
130 Some(dyn_bitcoin_rpc),
131 )
132 };
133
134 Self {
135 clients: ClientModuleInitRegistry::default(),
136 servers: ServerModuleInitRegistry::default(),
137 bitcoin_rpc: config,
138 fake_bitcoin_rpc,
139 bitcoin,
140 server_bitcoin_rpc: bitcoin_rpc_connection,
141 primary_module_kind: IClientModuleInit::module_kind(&client),
142 }
143 .with_module(client, server)
144 }
145
146 pub fn is_real_test() -> bool {
147 env::var(FM_TEST_USE_REAL_DAEMONS_ENV) == Ok("1".to_string())
148 }
149
150 pub fn with_module(
152 mut self,
153 client: impl IClientModuleInit + 'static,
154 server: impl IServerModuleInit + MaybeSend + MaybeSync + 'static,
155 ) -> Self {
156 self.clients.attach(DynClientModuleInit::from(client));
157 self.servers.attach(DynServerModuleInit::from(server));
158 self
159 }
160
161 pub fn with_server_only_module(
162 mut self,
163 server: impl IServerModuleInit + MaybeSend + MaybeSync + 'static,
164 ) -> Self {
165 self.servers.attach(DynServerModuleInit::from(server));
166 self
167 }
168
169 pub async fn new_fed_degraded(&self) -> FederationTest {
171 self.new_fed_builder(1).build().await
172 }
173
174 pub async fn new_fed_not_degraded(&self) -> FederationTest {
176 self.new_fed_builder(0).build().await
177 }
178
179 pub fn new_fed_builder(&self, num_offline: u16) -> FederationTestBuilder {
182 FederationTestBuilder::new(
183 self.servers.clone(),
184 self.clients.clone(),
185 self.primary_module_kind.clone(),
186 num_offline,
187 self.server_bitcoin_rpc(),
188 )
189 }
190
191 pub async fn new_gateway(&self) -> Gateway {
193 let module_kinds: Vec<_> = self
196 .servers
197 .iter()
198 .enumerate()
199 .map(|(id, (kind, _))| (id as ModuleInstanceId, kind.clone()))
200 .collect();
201 let decoders = self
202 .servers
203 .available_decoders(module_kinds.iter().map(|(id, kind)| (*id, kind)))
204 .unwrap();
205 let gateway_db = Database::new(MemDatabase::new(), decoders.clone());
206
207 let registry = self
208 .clients
209 .iter()
210 .filter(|(kind, _)| {
211 **kind != ModuleKind::from_static_str("ln")
213 })
214 .filter(|(kind, _)| {
215 **kind != ModuleKind::from_static_str("lnv2")
217 })
218 .map(|(_, client)| client.clone())
219 .collect();
220
221 let (path, _config_dir) = test_dir(&format!("gateway-{}", rand::random::<u64>()));
222
223 let client_builder: GatewayClientBuilder =
225 GatewayClientBuilder::new(path.clone(), registry, DatabaseBackend::RocksDb)
226 .await
227 .expect("Failed to initialize gateway");
228
229 let ln_client: Arc<dyn ILnRpcClient> = Arc::new(FakeLightningTest::new());
230
231 let LightningInfo::Connected {
232 public_key: lightning_public_key,
233 alias: lightning_alias,
234 network: lightning_network,
235 block_height: _,
236 synced_to_chain: _,
237 } = ln_client.parsed_node_info().await
238 else {
239 panic!("Could not connect to Lightning node")
240 };
241 let lightning_context = LightningContext {
242 lnrpc: ln_client.clone(),
243 lightning_public_key,
244 lightning_alias,
245 lightning_network,
246 };
247
248 let listen: SocketAddr = "127.0.0.1:9000".parse().unwrap();
250 let address: SafeUrl = format!("http://{listen}").parse().unwrap();
251
252 let esplora_server_url = SafeUrl::parse(&format!(
253 "http://127.0.0.1:{}",
254 env::var(FM_PORT_ESPLORA_ENV).unwrap_or(String::from("50002"))
255 ))
256 .expect("Failed to parse default esplora server");
257 let bitcoin_rpc = fedimint_bitcoind::EsploraClient::new(&esplora_server_url)
258 .expect("Could not create EsploraClient")
259 .into_dyn();
260 let esplora_chain_source = ChainSource::Esplora {
261 server_url: esplora_server_url,
262 };
263
264 Gateway::new_with_custom_registry(
265 LightningMode::Lnd {
268 lnd_rpc_addr: "FakeRpcAddr".to_string(),
269 lnd_tls_cert: "FakeTlsCert".to_string(),
270 lnd_macaroon: "FakeMacaroon".to_string(),
271 },
272 client_builder,
273 listen,
274 address.clone(),
275 bcrypt::HashParts::from_str(
276 &bcrypt::hash(DEFAULT_GATEWAY_PASSWORD, bcrypt::DEFAULT_COST).unwrap(),
277 )
278 .unwrap(),
279 bitcoin::Network::Regtest,
280 0,
281 gateway_db,
282 fedimint_gateway_server::GatewayState::Running { lightning_context },
286 esplora_chain_source,
287 None,
288 bitcoin_rpc,
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}