@@ -11,6 +11,7 @@ mod common;
1111
1212use std:: str:: FromStr ;
1313
14+ use base64:: prelude:: { Engine as _, BASE64_STANDARD } ;
1415use common:: eclair:: TestEclairNode ;
1516use common:: external_node:: ExternalNode ;
1617use common:: scenarios:: channel:: {
@@ -31,13 +32,17 @@ use electrsd::corepc_node::Client as BitcoindClient;
3132use electrum_client:: Client as ElectrumClient ;
3233use ldk_node:: { Builder , Event } ;
3334
34- /// Run a shell command via `spawn_blocking` to avoid blocking the tokio runtime.
35- async fn run_cmd ( program : & str , args : & [ & str ] ) -> std:: io:: Result < std:: process:: Output > {
36- let program = program. to_string ( ) ;
37- let args: Vec < String > = args. iter ( ) . map ( |s| s. to_string ( ) ) . collect ( ) ;
38- tokio:: task:: spawn_blocking ( move || std:: process:: Command :: new ( & program) . args ( & args) . output ( ) )
39- . await
40- . expect ( "spawn_blocking panicked" )
35+ /// Unlock all UTXOs in the given bitcoind wallet via JSON-RPC.
36+ async fn unlock_utxos ( wallet_url : & str , user : & str , pass : & str ) {
37+ let auth = BASE64_STANDARD . encode ( format ! ( "{}:{}" , user, pass) ) ;
38+ let body = r#"{"jsonrpc":"1.0","method":"lockunspent","params":[true]}"# ;
39+ let _ = bitreq:: post ( wallet_url)
40+ . with_header ( "Authorization" , format ! ( "Basic {}" , auth) )
41+ . with_header ( "Content-Type" , "text/plain" )
42+ . with_body ( body)
43+ . with_timeout ( 5 )
44+ . send_async ( )
45+ . await ;
4146}
4247
4348async fn setup_clients ( ) -> ( BitcoindClient , ElectrumClient , TestEclairNode ) {
@@ -53,99 +58,8 @@ async fn setup_clients() -> (BitcoindClient, ElectrumClient, TestEclairNode) {
5358 . unwrap ( ) ;
5459 let electrs = ElectrumClient :: new ( "tcp://127.0.0.1:50001" ) . unwrap ( ) ;
5560
56- // Recreate the Eclair container between tests to give a fresh /data
57- // directory, a new seed, and a clean initialization against the current
58- // chain tip.
59- let container_name =
60- std:: env:: var ( "ECLAIR_CONTAINER_NAME" ) . unwrap_or_else ( |_| "ldk-node-eclair-1" . to_string ( ) ) ;
61- run_cmd ( "docker" , & [ "rm" , "-f" , & container_name] ) . await . ok ( ) ;
62-
63- // Unlock UTXOs and start Eclair, retrying if locked UTXOs remain.
64- // Force-close transactions can lock new UTXOs between the unlock call
65- // and Eclair startup, so we may need multiple attempts.
66- let deadline = std:: time:: Instant :: now ( ) + std:: time:: Duration :: from_secs ( 90 ) ;
67- let mut attempt = 0 ;
68- loop {
69- // Unlock any UTXOs left locked in the Eclair wallet.
70- run_cmd (
71- "curl" ,
72- & [
73- "-s" ,
74- "--max-time" ,
75- "5" ,
76- "--user" ,
77- "user:pass" ,
78- "--data-binary" ,
79- r#"{"jsonrpc":"1.0","method":"lockunspent","params":[true]}"# ,
80- "-H" ,
81- "content-type: text/plain;" ,
82- "http://127.0.0.1:18443/wallet/eclair" ,
83- ] ,
84- )
85- . await
86- . ok ( ) ;
87-
88- if attempt > 0 {
89- // On retry, recreate the container since Eclair exited.
90- run_cmd ( "docker" , & [ "rm" , "-f" , & container_name] ) . await . ok ( ) ;
91- }
92- let output = run_cmd (
93- "docker" ,
94- & [ "compose" , "-f" , "docker-compose-eclair.yml" , "up" , "-d" , "eclair" ] ,
95- )
96- . await
97- . expect ( "failed to spawn docker compose" ) ;
98- assert ! (
99- output. status. success( ) ,
100- "docker compose up failed (exit {}): {}" ,
101- output. status,
102- String :: from_utf8_lossy( & output. stderr) ,
103- ) ;
104-
105- // Wait for Eclair to become ready.
106- let mut ready = false ;
107- for _ in 0 ..30 {
108- if std:: time:: Instant :: now ( ) >= deadline {
109- let logs = run_cmd ( "docker" , & [ "logs" , "--tail" , "50" , & container_name] ) . await . ok ( ) ;
110- if let Some ( l) = logs {
111- eprintln ! (
112- "=== Eclair container logs ===\n {}{}" ,
113- String :: from_utf8_lossy( & l. stdout) ,
114- String :: from_utf8_lossy( & l. stderr)
115- ) ;
116- }
117- panic ! ( "Eclair did not start within 90s (after {} attempts)" , attempt + 1 ) ;
118- }
119- tokio:: time:: sleep ( std:: time:: Duration :: from_secs ( 1 ) ) . await ;
120- ready = run_cmd (
121- "curl" ,
122- & [
123- "-s" ,
124- "--max-time" ,
125- "2" ,
126- "-u" ,
127- ":eclairpassword" ,
128- "-X" ,
129- "POST" ,
130- "http://127.0.0.1:8080/getinfo" ,
131- ] ,
132- )
133- . await
134- . map ( |o| o. status . success ( ) && !o. stdout . is_empty ( ) )
135- . unwrap_or ( false ) ;
136- if ready {
137- break ;
138- }
139- }
140-
141- if ready {
142- break ;
143- }
144-
145- // Eclair likely failed due to locked UTXOs — retry.
146- attempt += 1 ;
147- eprintln ! ( "Eclair failed to start (attempt {}), retrying after lockunspent..." , attempt) ;
148- }
61+ // Unlock any UTXOs left locked by previous force-close tests.
62+ unlock_utxos ( "http://127.0.0.1:18443/wallet/eclair" , "user" , "pass" ) . await ;
14963
15064 let eclair = TestEclairNode :: from_env ( ) ;
15165 ( bitcoind, electrs, eclair)
0 commit comments