Skip to content

Commit 17c9661

Browse files
committed
handle fallback for propsal failing
1 parent 44354f1 commit 17c9661

File tree

2 files changed

+156
-55
lines changed

2 files changed

+156
-55
lines changed

payjoin-cli/src/app/v1.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,7 @@ impl AppTrait for App {
7070
.create_v1_post_request();
7171
let http = http_agent(&self.config)?;
7272
let body = String::from_utf8(req.body.clone()).unwrap();
73-
println!("Sending fallback request to {}", &req.url);
74-
let response = http
75-
.post(req.url)
76-
.header("Content-Type", req.content_type)
77-
.body(body.clone())
78-
.send()
79-
.await
80-
.with_context(|| "HTTP request failed")?;
73+
8174
let fallback_tx = Psbt::from_str(&body)
8275
.map_err(|e| anyhow!("Failed to load PSBT from base64: {}", e))?
8376
.extract_tx()?;
@@ -86,6 +79,26 @@ impl AppTrait for App {
8679
"Sent fallback transaction hex: {:#}",
8780
payjoin::bitcoin::consensus::encode::serialize_hex(&fallback_tx)
8881
);
82+
println!("Sending fallback request to {}", &req.url);
83+
84+
85+
let response = match http
86+
.post(req.url)
87+
.header("Content-Type", req.content_type)
88+
.body(body.clone())
89+
.send()
90+
.await
91+
{
92+
Ok(resp) => resp,
93+
Err(e) => {
94+
tracing::debug!("HTTP request failed: {e:?}");
95+
println!("Payjoin request failed: {e}. Broadcasting fallback transaction.");
96+
let txid = self.wallet().broadcast_tx(&fallback_tx)?;
97+
println!("Fallback transaction broadcasted. TXID: {txid}");
98+
return Ok(());
99+
}
100+
};
101+
89102
// Try to process the payjoin response
90103
match ctx.process_response(&response.bytes().await?) {
91104
Ok(psbt) => {

payjoin-cli/tests/e2e.rs

Lines changed: 135 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,141 @@ mod e2e {
171171
.write_all(format!("{line}\n").as_bytes())
172172
.await
173173
.expect("Failed to write to stdout");
174-
if line.contains("Payjoin sent") {
174+
if line.contains("Payjoin sent")
175+
|| line.contains("Fallback transaction broadcasted")
176+
{
177+
let _ = tx.send(true).await;
178+
break;
179+
}
180+
}
181+
});
182+
183+
let timeout = tokio::time::Duration::from_secs(10);
184+
let payjoin_sent = tokio::time::timeout(timeout, rx.recv())
185+
.await
186+
.unwrap_or(Some(false)) // timed out
187+
.expect("rx channel closed prematurely"); // recv() returned None
188+
189+
terminate(cli_receiver).await.expect("Failed to kill payjoin-cli");
190+
terminate(cli_sender).await.expect("Failed to kill payjoin-cli");
191+
192+
payjoin_sent
193+
})
194+
.await?;
195+
196+
assert!(payjoin_sent, "Payjoin send was not detected");
197+
198+
Ok(())
199+
}
200+
201+
#[cfg(feature = "v1")]
202+
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
203+
async fn send_receive_payjoin_v1_fallback_transaction_handling() -> Result<(), BoxError> {
204+
use payjoin_test_utils::local_cert_key;
205+
206+
let (bitcoind, _sender, _receiver) = init_bitcoind_sender_receiver(None, None)?;
207+
let temp_dir = tempdir()?;
208+
let receiver_db_path = temp_dir.path().join("receiver_db");
209+
let sender_db_path = temp_dir.path().join("sender_db");
210+
211+
let payjoin_sent = tokio::spawn(async move {
212+
let receiver_rpchost = format!("http://{}/wallet/receiver", bitcoind.params.rpc_socket);
213+
let sender_rpchost = format!("http://{}/wallet/sender", bitcoind.params.rpc_socket);
214+
let cookie_file = &bitcoind.params.cookie_file;
215+
let pj_endpoint = "https://localhost";
216+
let payjoin_cli = env!("CARGO_BIN_EXE_payjoin-cli");
217+
218+
let cert = local_cert_key();
219+
let cert_path = &temp_dir.path().join("localhost.crt");
220+
tokio::fs::write(cert_path, cert.cert.der().to_vec())
221+
.await
222+
.expect("must be able to write self signed certificate");
223+
224+
let key_path = &temp_dir.path().join("localhost.key");
225+
tokio::fs::write(key_path, cert.signing_key.serialize_der())
226+
.await
227+
.expect("must be able to write self signed certificate");
228+
229+
let mut cli_receiver = Command::new(payjoin_cli)
230+
.arg("--root-certificate")
231+
.arg(cert_path)
232+
.arg("--certificate-key")
233+
.arg(key_path)
234+
.arg("--bip78")
235+
.arg("--rpchost")
236+
.arg(&receiver_rpchost)
237+
.arg("--cookie-file")
238+
.arg(cookie_file)
239+
.arg("--db-path")
240+
.arg(&receiver_db_path)
241+
.arg("receive")
242+
.arg(RECEIVE_SATS)
243+
.arg("--port")
244+
.arg("0")
245+
.arg("--pj-endpoint")
246+
.arg("https://dfdkfkdj.codm")
247+
.stdout(Stdio::piped())
248+
.stderr(Stdio::inherit())
249+
.spawn()
250+
.expect("Failed to execute payjoin-cli");
251+
252+
let stdout =
253+
cli_receiver.stdout.take().expect("Failed to take stdout of child process");
254+
let reader = BufReader::new(stdout);
255+
let mut stdout = tokio::io::stdout();
256+
let mut bip21 = String::new();
257+
258+
let mut lines = reader.lines();
259+
260+
while let Some(line) = lines.next_line().await.expect("Failed to read line from stdout")
261+
{
262+
// Write to stdout regardless
263+
stdout
264+
.write_all(format!("{line}\n").as_bytes())
265+
.await
266+
.expect("Failed to write to stdout");
267+
268+
if line.to_ascii_uppercase().starts_with("BITCOIN") {
269+
bip21 = line;
270+
break;
271+
}
272+
}
273+
tracing::debug!("Got bip21 {}", &bip21);
274+
275+
let mut cli_sender = Command::new(payjoin_cli)
276+
.arg("--root-certificate")
277+
.arg(cert_path)
278+
.arg("--bip78")
279+
.arg("--rpchost")
280+
.arg(&sender_rpchost)
281+
.arg("--cookie-file")
282+
.arg(cookie_file)
283+
.arg("--db-path")
284+
.arg(&sender_db_path)
285+
.arg("send")
286+
.arg(&bip21)
287+
.arg("--fee-rate")
288+
.arg("1")
289+
.stdout(Stdio::piped())
290+
.stderr(Stdio::inherit())
291+
.spawn()
292+
.expect("Failed to execute payjoin-cli");
293+
294+
let stdout = cli_sender.stdout.take().expect("Failed to take stdout of child process");
295+
let reader = BufReader::new(stdout);
296+
let (tx, mut rx) = tokio::sync::mpsc::channel(1);
297+
298+
let mut lines = reader.lines();
299+
tokio::spawn(async move {
300+
let mut stdout = tokio::io::stdout();
301+
while let Some(line) =
302+
lines.next_line().await.expect("Failed to read line from stdout")
303+
{
304+
stdout
305+
.write_all(format!("{line}\n").as_bytes())
306+
.await
307+
.expect("Failed to write to stdout");
308+
if line.contains("Fallback transaction broadcasted") {
175309
let _ = tx.send(true).await;
176310
break;
177311
}
@@ -621,50 +755,4 @@ mod e2e {
621755

622756
Ok(())
623757
}
624-
625-
/// Test that verifies fallback transaction is broadcasted when payjoin fails (V1)
626-
#[tokio::test]
627-
async fn test_v1_fallback_transaction_broadcast_on_payjoin_failure() -> Result<(), BoxError> {
628-
use payjoin_test_utils::local_cert_key;
629-
630-
let (bitcoind, _sender, _receiver) = init_bitcoind_sender_receiver(None, None)?;
631-
let temp_dir = tempdir()?;
632-
let sender_db_path = temp_dir.path().join("sender.db");
633-
let receiver_db_path = temp_dir.path().join("receiver.db");
634-
635-
let receiver_rpchost = format!("http://{}/wallet/receiver", bitcoind.params.rpc_socket);
636-
let sender_rpchost = format!("http://{}/wallet/sender", bitcoind.params.rpc_socket);
637-
let cookie_file = &bitcoind.params.cookie_file;
638-
let payjoin_cli = env!("CARGO_BIN_EXE_payjoin-cli");
639-
640-
let cert = local_cert_key();
641-
let cert_path = &temp_dir.path().join("localhost.crt");
642-
tokio::fs::write(cert_path, cert.cert.der().to_vec())
643-
.await
644-
.expect("must be able to write self signed certificate");
645-
646-
Ok(())
647-
}
648-
649-
/// Test that verifies fallback transaction is broadcasted when payjoin fails (V2)
650-
#[tokio::test]
651-
async fn test_v2_fallback_transaction_broadcast_on_payjoin_failure() -> Result<(), BoxError> {
652-
use payjoin_test_utils::local_cert_key;
653-
654-
let (bitcoind, _sender, _receiver) = init_bitcoind_sender_receiver(None, None)?;
655-
let temp_dir = tempdir()?;
656-
let sender_db_path = temp_dir.path().join("sender.db");
657-
658-
let sender_rpchost = format!("http://{}/wallet/sender", bitcoind.params.rpc_socket);
659-
let cookie_file = &bitcoind.params.cookie_file;
660-
let payjoin_cli = env!("CARGO_BIN_EXE_payjoin-cli");
661-
662-
let cert = local_cert_key();
663-
let cert_path = &temp_dir.path().join("localhost.crt");
664-
tokio::fs::write(cert_path, cert.cert.der().to_vec())
665-
.await
666-
.expect("must be able to write self signed certificate");
667-
668-
Ok(())
669-
}
670758
}

0 commit comments

Comments
 (0)