@@ -196,6 +196,123 @@ mod e2e {
196196 Ok ( ( ) )
197197 }
198198
199+ #[ cfg( feature = "v2" ) ]
200+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 4 ) ]
201+ async fn send_receive_payjoin_v2_fallback_transaction_handling (
202+ ) -> Result < ( ) , Box < dyn std:: error:: Error + Send + Sync > > {
203+ use payjoin_test_utils:: { init_tracing, TestServices } ;
204+ use tempfile:: TempDir ;
205+ use tokio:: process:: Child ;
206+
207+ type Result < T > = std:: result:: Result < T , BoxError > ;
208+
209+ init_tracing ( ) ;
210+ let mut services = TestServices :: initialize ( ) . await ?;
211+ let temp_dir = tempdir ( ) ?;
212+
213+ let result = tokio:: select! {
214+ res = services. take_ohttp_relay_handle( ) => Err ( format!( "Ohttp relay is long running: {res:?}" ) . into( ) ) ,
215+ res = services. take_directory_handle( ) => Err ( format!( "Directory server is long running: {res:?}" ) . into( ) ) ,
216+ res = send_receive_fallback_async( & services, & temp_dir) => res,
217+ } ;
218+
219+ assert ! ( result. is_ok( ) , "v2 fallback test failed: {:#?}" , result. unwrap_err( ) ) ;
220+
221+ async fn send_receive_fallback_async (
222+ services : & TestServices ,
223+ temp_dir : & TempDir ,
224+ ) -> Result < ( ) > {
225+ let receiver_db_path = temp_dir. path ( ) . join ( "receiver_db" ) ;
226+ let sender_db_path = temp_dir. path ( ) . join ( "sender_db" ) ;
227+ let ( bitcoind, _sender, _receiver) = init_bitcoind_sender_receiver ( None , None ) ?;
228+ let cert_path = & temp_dir. path ( ) . join ( "localhost.der" ) ;
229+ tokio:: fs:: write ( cert_path, services. cert ( ) ) . await ?;
230+ services. wait_for_services_ready ( ) . await ?;
231+ let ohttp_keys = services. fetch_ohttp_keys ( ) . await ?;
232+ let ohttp_keys_path = temp_dir. path ( ) . join ( "ohttp_keys" ) ;
233+ tokio:: fs:: write ( & ohttp_keys_path, ohttp_keys. encode ( ) ?) . await ?;
234+
235+ let receiver_rpchost = format ! ( "http://{}/wallet/receiver" , bitcoind. params. rpc_socket) ;
236+ let sender_rpchost = format ! ( "http://{}/wallet/sender" , bitcoind. params. rpc_socket) ;
237+ let cookie_file = & bitcoind. params . cookie_file ;
238+
239+ let payjoin_cli = env ! ( "CARGO_BIN_EXE_payjoin-cli" ) ;
240+
241+ // Use an invalid directory URL to trigger fallback behavior
242+ let invalid_directory = "https://random-does-not-exist.com" ;
243+ let ohttp_relay = & services. ohttp_relay_url ( ) ;
244+
245+ // Start receiver with invalid directory to simulate failure scenario
246+ let cli_receive_initiator = Command :: new ( payjoin_cli)
247+ . arg ( "--root-certificate" )
248+ . arg ( cert_path)
249+ . arg ( "--rpchost" )
250+ . arg ( & receiver_rpchost)
251+ . arg ( "--cookie-file" )
252+ . arg ( cookie_file)
253+ . arg ( "--db-path" )
254+ . arg ( & receiver_db_path)
255+ . arg ( "--ohttp-relays" )
256+ . arg ( ohttp_relay)
257+ . arg ( "receive" )
258+ . arg ( RECEIVE_SATS )
259+ . arg ( "--pj-directory" )
260+ . arg ( invalid_directory)
261+ . arg ( "--ohttp-keys" )
262+ . arg ( & ohttp_keys_path)
263+ . stdout ( Stdio :: piped ( ) )
264+ . stderr ( Stdio :: inherit ( ) )
265+ . spawn ( )
266+ . expect ( "Failed to execute payjoin-cli" ) ;
267+
268+ let bip21 = get_bip21_from_receiver ( cli_receive_initiator) . await ;
269+
270+ // Send with the BIP21 URI - should broadcast fallback transaction
271+ let cli_send_initiator = Command :: new ( payjoin_cli)
272+ . arg ( "--root-certificate" )
273+ . arg ( cert_path)
274+ . arg ( "--rpchost" )
275+ . arg ( & sender_rpchost)
276+ . arg ( "--cookie-file" )
277+ . arg ( cookie_file)
278+ . arg ( "--db-path" )
279+ . arg ( & sender_db_path)
280+ . arg ( "--ohttp-relays" )
281+ . arg ( ohttp_relay)
282+ . arg ( "send" )
283+ . arg ( & bip21)
284+ . arg ( "--fee-rate" )
285+ . arg ( "1" )
286+ . stdout ( Stdio :: piped ( ) )
287+ . stderr ( Stdio :: inherit ( ) )
288+ . spawn ( )
289+ . expect ( "Failed to execute payjoin-cli" ) ;
290+
291+ check_fallback_broadcasted ( cli_send_initiator) . await ?;
292+
293+ Ok ( ( ) )
294+ }
295+
296+ async fn check_fallback_broadcasted ( mut cli_sender : Child ) -> Result < ( ) > {
297+ let mut stdout =
298+ cli_sender. stdout . take ( ) . expect ( "failed to take stdout of child process" ) ;
299+ let timeout = tokio:: time:: Duration :: from_secs ( 35 ) ;
300+ let res = tokio:: time:: timeout (
301+ timeout,
302+ wait_for_stdout_match ( & mut stdout, |line| {
303+ line. contains ( "Fallback transaction broadcasted" )
304+ } ) ,
305+ )
306+ . await ?;
307+
308+ terminate ( cli_sender) . await . expect ( "Failed to kill payjoin-cli sender" ) ;
309+ assert ! ( res. is_some( ) , "Fallback transaction broadcast was not detected" ) ;
310+ Ok ( ( ) )
311+ }
312+
313+ Ok ( ( ) )
314+ }
315+
199316 #[ cfg( feature = "v1" ) ]
200317 #[ tokio:: test( flavor = "multi_thread" , worker_threads = 4 ) ]
201318 async fn send_receive_payjoin_v1_fallback_transaction_handling ( ) -> Result < ( ) , BoxError > {
0 commit comments