@@ -84,35 +84,54 @@ impl TestBitcoind {
8484 }
8585}
8686
87- /// Handle to a running ldk-server child process.
88- pub struct LdkServerHandle {
89- child : Option < Child > ,
87+ /// Wrapper around an electrsd process providing both Electrum and Esplora endpoints.
88+ pub struct TestElectrs {
89+ pub electrsd : electrsd:: ElectrsD ,
90+ }
91+
92+ impl TestElectrs {
93+ /// Start an electrs instance connected to the given bitcoind with Esplora HTTP enabled.
94+ pub fn new ( bitcoind : & TestBitcoind ) -> Self {
95+ let mut conf = electrsd:: Conf :: default ( ) ;
96+ conf. http_enabled = true ;
97+ let electrsd =
98+ electrsd:: ElectrsD :: with_conf ( electrsd:: exe_path ( ) . unwrap ( ) , & bitcoind. bitcoind , & conf)
99+ . unwrap ( ) ;
100+ Self { electrsd }
101+ }
102+
103+ pub fn electrum_url ( & self ) -> String {
104+ // electrsd binds to 0.0.0.0 but that's not a connectable address for clients
105+ self . electrsd . electrum_url . replace ( "0.0.0.0" , "127.0.0.1" )
106+ }
107+
108+ pub fn esplora_url ( & self ) -> String {
109+ let url = self . electrsd . esplora_url . as_ref ( ) . expect ( "esplora not enabled" ) ;
110+ // electrsd binds to 0.0.0.0 but that's not a connectable address for clients
111+ format ! ( "http://{}" , url. replace( "0.0.0.0" , "127.0.0.1" ) )
112+ }
113+
114+ /// Trigger electrs to sync with bitcoind.
115+ pub fn trigger ( & self ) {
116+ self . electrsd . trigger ( ) . unwrap ( ) ;
117+ }
118+ }
119+
120+ /// Dynamic parameters available when building test configs.
121+ pub struct TestServerParams {
90122 pub rest_port : u16 ,
91123 pub p2p_port : u16 ,
92124 pub storage_dir : PathBuf ,
93- pub api_key : String ,
94- pub tls_cert_path : PathBuf ,
95- pub node_id : String ,
125+ pub rpc_address : String ,
126+ pub rpc_user : String ,
127+ pub rpc_password : String ,
96128 pub exchange_name : String ,
97- client : LdkServerClient ,
98129}
99130
100- impl LdkServerHandle {
101- /// Starts a new ldk-server instance against the given bitcoind.
102- /// Waits until the server is ready to accept requests.
103- pub async fn start ( bitcoind : & TestBitcoind ) -> Self {
104- #[ allow( deprecated) ]
105- let storage_dir = tempfile:: tempdir ( ) . unwrap ( ) . into_path ( ) ;
106- let rest_port = find_available_port ( ) ;
107- let p2p_port = find_available_port ( ) ;
108-
109- let ( rpc_host, rpc_port_num, rpc_user, rpc_password) = bitcoind. rpc_details ( ) ;
110- let rpc_address = format ! ( "{rpc_host}:{rpc_port_num}" ) ;
111-
112- let exchange_name = format ! ( "e2e_test_exchange_{rest_port}" ) ;
113-
114- let config_content = format ! (
115- r#"[node]
131+ /// Generate a test config TOML with a custom chain source section.
132+ pub fn test_config_with_chain_source ( params : & TestServerParams , chain_source_toml : & str ) -> String {
133+ format ! (
134+ r#"[node]
116135network = "regtest"
117136listening_addresses = ["127.0.0.1:{p2p_port}"]
118137rest_service_address = "127.0.0.1:{rest_port}"
@@ -121,10 +140,7 @@ alias = "e2e-test-node"
121140[storage.disk]
122141dir_path = "{storage_dir}"
123142
124- [bitcoind]
125- rpc_address = "{rpc_address}"
126- rpc_user = "{rpc_user}"
127- rpc_password = "{rpc_password}"
143+ {chain_source}
128144
129145[rabbitmq]
130146connection_string = "amqp://guest:guest@localhost:5672/%2f"
@@ -141,21 +157,81 @@ min_payment_size_msat = 0
141157max_payment_size_msat = 1000000000
142158client_trusts_lsp = true
143159"# ,
144- storage_dir = storage_dir. display( ) ,
145- ) ;
160+ p2p_port = params. p2p_port,
161+ rest_port = params. rest_port,
162+ storage_dir = params. storage_dir. display( ) ,
163+ chain_source = chain_source_toml,
164+ exchange_name = params. exchange_name,
165+ )
166+ }
167+
168+ /// Generate the default test config TOML with bitcoind RPC chain source.
169+ pub fn default_test_config ( params : & TestServerParams ) -> String {
170+ let chain_source = format ! (
171+ "[bitcoind]\n rpc_address = \" {}\" \n rpc_user = \" {}\" \n rpc_password = \" {}\" " ,
172+ params. rpc_address, params. rpc_user, params. rpc_password
173+ ) ;
174+ test_config_with_chain_source ( params, & chain_source)
175+ }
146176
147- let config_path = storage_dir. join ( "config.toml" ) ;
148- std:: fs:: write ( & config_path, & config_content) . unwrap ( ) ;
177+ /// Handle to a running ldk-server child process.
178+ pub struct LdkServerHandle {
179+ child : Option < Child > ,
180+ pub rest_port : u16 ,
181+ pub p2p_port : u16 ,
182+ pub storage_dir : PathBuf ,
183+ pub api_key : String ,
184+ pub tls_cert_path : PathBuf ,
185+ pub node_id : String ,
186+ pub exchange_name : String ,
187+ client : LdkServerClient ,
188+ // Kept alive so the electrs process doesn't get dropped
189+ _electrs : Option < TestElectrs > ,
190+ }
191+
192+ impl LdkServerHandle {
193+ /// Starts a new ldk-server instance against the given bitcoind.
194+ /// Randomly picks between bitcoind RPC, electrum, and esplora as the chain source.
195+ pub async fn start ( bitcoind : & TestBitcoind ) -> Self {
196+ match rand:: random :: < u8 > ( ) % 3 {
197+ 0 => Self :: start_with_config ( bitcoind, default_test_config) . await ,
198+ 1 => {
199+ let electrs = TestElectrs :: new ( bitcoind) ;
200+ let url = electrs. electrum_url ( ) ;
201+ let mut handle = Self :: start_with_config ( bitcoind, move |params| {
202+ test_config_with_chain_source (
203+ params,
204+ & format ! ( "[electrum]\n server_url = \" {}\" " , url) ,
205+ )
206+ } )
207+ . await ;
208+ handle. _electrs = Some ( electrs) ;
209+ handle
210+ } ,
211+ 2 => {
212+ let electrs = TestElectrs :: new ( bitcoind) ;
213+ let url = electrs. esplora_url ( ) ;
214+ let mut handle = Self :: start_with_config ( bitcoind, move |params| {
215+ test_config_with_chain_source (
216+ params,
217+ & format ! ( "[esplora]\n server_url = \" {}\" " , url) ,
218+ )
219+ } )
220+ . await ;
221+ handle. _electrs = Some ( electrs) ;
222+ handle
223+ } ,
224+ _ => unreachable ! ( ) ,
225+ }
226+ }
149227
150- let server_binary = server_binary_path ( ) ;
151- let mut child = Command :: new ( & server_binary)
152- . arg ( config_path. to_str ( ) . unwrap ( ) )
153- . stdout ( Stdio :: piped ( ) )
154- . stderr ( Stdio :: piped ( ) )
155- . spawn ( )
156- . unwrap_or_else ( |e| {
157- panic ! ( "Failed to start ldk-server binary at {:?}: {}" , server_binary, e)
158- } ) ;
228+ /// Starts a new ldk-server instance with a custom config.
229+ /// The `config_fn` receives dynamic test parameters and returns the full TOML config string.
230+ pub async fn start_with_config (
231+ bitcoind : & TestBitcoind , config_fn : impl FnOnce ( & TestServerParams ) -> String ,
232+ ) -> Self {
233+ let ( mut child, params) = spawn_server ( bitcoind, config_fn) ;
234+ let TestServerParams { rest_port, p2p_port, storage_dir, exchange_name, .. } = params;
159235
160236 // Spawn threads to forward stdout and stderr for debugging
161237 let stdout = child. stdout . take ( ) . unwrap ( ) ;
@@ -204,6 +280,7 @@ client_trusts_lsp = true
204280 node_id : String :: new ( ) ,
205281 exchange_name,
206282 client,
283+ _electrs : None ,
207284 } ;
208285
209286 // Wait for server to be ready and get node info
@@ -235,6 +312,48 @@ impl Drop for LdkServerHandle {
235312 }
236313}
237314
315+ /// Prepare test server params and spawn the ldk-server process.
316+ fn spawn_server (
317+ bitcoind : & TestBitcoind , config_fn : impl FnOnce ( & TestServerParams ) -> String ,
318+ ) -> ( Child , TestServerParams ) {
319+ #[ allow( deprecated) ]
320+ let storage_dir = tempfile:: tempdir ( ) . unwrap ( ) . into_path ( ) ;
321+ let rest_port = find_available_port ( ) ;
322+ let p2p_port = find_available_port ( ) ;
323+
324+ let ( rpc_host, rpc_port_num, rpc_user, rpc_password) = bitcoind. rpc_details ( ) ;
325+ let rpc_address = format ! ( "{rpc_host}:{rpc_port_num}" ) ;
326+
327+ let exchange_name = format ! ( "e2e_test_exchange_{rest_port}" ) ;
328+
329+ let params = TestServerParams {
330+ rest_port,
331+ p2p_port,
332+ storage_dir,
333+ rpc_address,
334+ rpc_user,
335+ rpc_password,
336+ exchange_name,
337+ } ;
338+
339+ let config_content = config_fn ( & params) ;
340+
341+ let config_path = params. storage_dir . join ( "config.toml" ) ;
342+ std:: fs:: write ( & config_path, & config_content) . unwrap ( ) ;
343+
344+ let server_binary = server_binary_path ( ) ;
345+ let child = Command :: new ( & server_binary)
346+ . arg ( config_path. to_str ( ) . unwrap ( ) )
347+ . stdout ( Stdio :: piped ( ) )
348+ . stderr ( Stdio :: piped ( ) )
349+ . spawn ( )
350+ . unwrap_or_else ( |e| {
351+ panic ! ( "Failed to start ldk-server binary at {:?}: {}" , server_binary, e)
352+ } ) ;
353+
354+ ( child, params)
355+ }
356+
238357/// Find an available TCP port by binding to port 0.
239358pub fn find_available_port ( ) -> u16 {
240359 let listener = TcpListener :: bind ( "127.0.0.1:0" ) . unwrap ( ) ;
0 commit comments