Skip to content

Commit 3d44e43

Browse files
committed
handle fallback for propsal failing
1 parent b25f2a6 commit 3d44e43

File tree

1 file changed

+64
-34
lines changed

1 file changed

+64
-34
lines changed

payjoin-cli/src/app/v2/mod.rs

Lines changed: 64 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex};
44
use anyhow::{anyhow, Context, Result};
55
use payjoin::bitcoin::consensus::encode::serialize_hex;
66
use payjoin::bitcoin::{Amount, FeeRate};
7-
use payjoin::persist::OptionalTransitionOutcome;
7+
use payjoin::persist::{OptionalTransitionOutcome, PersistedError};
88
use payjoin::receive::v2::{
99
replay_event_log as replay_receiver_event_log, HasReplyableError, Initialized,
1010
MaybeInputsOwned, MaybeInputsSeen, Monitor, OutputsUnknown, PayjoinProposal,
@@ -13,8 +13,8 @@ use payjoin::receive::v2::{
1313
WantsOutputs,
1414
};
1515
use payjoin::send::v2::{
16-
replay_event_log as replay_sender_event_log, PollingForProposal, SendSession, Sender,
17-
SenderBuilder, SessionOutcome as SenderSessionOutcome, WithReplyKey,
16+
replay_event_log as replay_sender_event_log, EncapsulationError, PollingForProposal,
17+
SendSession, Sender, SenderBuilder, SessionOutcome as SenderSessionOutcome, WithReplyKey,
1818
};
1919
use payjoin::{ImplementationError, PjParam, Uri};
2020
use tokio::sync::watch;
@@ -25,7 +25,7 @@ use super::App as AppTrait;
2525
use crate::app::v2::ohttp::{unwrap_ohttp_keys_or_else_fetch, RelayManager};
2626
use crate::app::{handle_interrupt, http_agent};
2727
use crate::db::v2::{ReceiverPersister, SenderPersister, SessionId};
28-
use crate::db::Database;
28+
use crate::db::{error as db_error, Database};
2929

3030
mod ohttp;
3131

@@ -215,42 +215,47 @@ impl AppTrait for App {
215215
}
216216
PjParam::V2(pj_param) => {
217217
let receiver_pubkey = pj_param.receiver_pubkey();
218-
let sender_state =
219-
self.db.get_send_session_ids()?.into_iter().find_map(|session_id| {
218+
let (sender_state, persister, fallback_tx) =
219+
match self.db.get_send_session_ids()?.into_iter().find_map(|session_id| {
220220
let session_receiver_pubkey = self
221221
.db
222222
.get_send_session_receiver_pk(&session_id)
223223
.expect("Receiver pubkey should exist if session id exists");
224224
if session_receiver_pubkey == *receiver_pubkey {
225225
let sender_persister =
226226
SenderPersister::from_id(self.db.clone(), session_id);
227-
let (send_session, _) = replay_sender_event_log(&sender_persister)
228-
.map_err(|e| anyhow!("Failed to replay sender event log: {:?}", e))
229-
.ok()?;
230-
231-
Some((send_session, sender_persister))
227+
let (send_session, history) =
228+
replay_sender_event_log(&sender_persister)
229+
.map_err(|e| {
230+
anyhow!("Failed to replay sender event log: {:?}", e)
231+
})
232+
.ok()?;
233+
234+
Some((send_session, sender_persister, history.fallback_tx()))
232235
} else {
233236
None
234237
}
235-
});
236-
237-
let (sender_state, persister) = match sender_state {
238-
Some((sender_state, persister)) => (sender_state, persister),
239-
None => {
240-
let persister =
241-
SenderPersister::new(self.db.clone(), receiver_pubkey.clone())?;
242-
let psbt = self.create_original_psbt(&address, amount, fee_rate)?;
243-
let sender =
244-
SenderBuilder::from_parts(psbt, pj_param, &address, Some(amount))
245-
.build_recommended(fee_rate)?
246-
.save(&persister)?;
247-
248-
(SendSession::WithReplyKey(sender), persister)
249-
}
250-
};
238+
}) {
239+
Some((sender_state, persister, fallback_tx)) =>
240+
(sender_state, persister, fallback_tx),
241+
None => {
242+
let persister =
243+
SenderPersister::new(self.db.clone(), receiver_pubkey.clone())?;
244+
let psbt = self.create_original_psbt(&address, amount, fee_rate)?;
245+
let fallback_tx = psbt.clone().extract_tx().map_err(|e| {
246+
anyhow!("Failed to extract fallback transaction: {}", e)
247+
})?;
248+
let sender =
249+
SenderBuilder::from_parts(psbt, pj_param, &address, Some(amount))
250+
.build_recommended(fee_rate)?
251+
.save(&persister)?;
252+
253+
(SendSession::WithReplyKey(sender), persister, fallback_tx)
254+
}
255+
};
251256
let mut interrupt = self.interrupt.clone();
252257
tokio::select! {
253-
_ = self.process_sender_session(sender_state, &persister) => return Ok(()),
258+
_ = self.process_sender_session(sender_state, &persister, &fallback_tx) => return Ok(()),
254259
_ = interrupt.changed() => {
255260
println!("Interrupted. Call `send` with the same arguments to resume this session or `resume` to resume all sessions.");
256261
return Err(anyhow!("Interrupted"))
@@ -310,12 +315,14 @@ impl AppTrait for App {
310315

311316
for session_id in send_session_ids {
312317
let sender_persiter = SenderPersister::from_id(self.db.clone(), session_id);
313-
let sender_state = replay_sender_event_log(&sender_persiter)
314-
.map_err(|e| anyhow!("Failed to replay sender event log: {:?}", e))?
315-
.0;
318+
let (sender_state, history) = replay_sender_event_log(&sender_persiter)
319+
.map_err(|e| anyhow!("Failed to replay sender event log: {:?}", e))?;
320+
let fallback_tx = history.fallback_tx();
316321
let self_clone = self.clone();
317322
tasks.push(tokio::spawn(async move {
318-
self_clone.process_sender_session(sender_state, &sender_persiter).await
323+
self_clone
324+
.process_sender_session(sender_state, &sender_persiter, &fallback_tx)
325+
.await
319326
}));
320327
}
321328

@@ -465,10 +472,33 @@ impl App {
465472
&self,
466473
session: SendSession,
467474
persister: &SenderPersister,
475+
fallback_tx: &payjoin::bitcoin::Transaction,
468476
) -> Result<()> {
469477
match session {
470-
SendSession::WithReplyKey(context) =>
471-
self.post_original_proposal(context, persister).await?,
478+
SendSession::WithReplyKey(context) => {
479+
let response = self.post_original_proposal(context, persister).await;
480+
match response {
481+
Ok(_) => {
482+
return Ok(());
483+
}
484+
Err(e) => {
485+
if let Some(persisted_error) = e.downcast_ref::<PersistedError<
486+
EncapsulationError,
487+
db_error::Error,
488+
(),
489+
>>() {
490+
if let Some(api_error) = persisted_error.api_error_ref() {
491+
println!("Error posting original proposal: {api_error}");
492+
let txid = self.wallet().broadcast_tx(&fallback_tx)?;
493+
println!("Fallback transaction broadcasted. TXID: {}", txid);
494+
return Err(anyhow!(
495+
"Fallback transaction broadcasted due to: {api_error}"
496+
));
497+
}
498+
}
499+
}
500+
}
501+
}
472502
SendSession::PollingForProposal(context) =>
473503
self.get_proposed_payjoin_psbt(context, persister).await?,
474504
SendSession::ProposalReceived(proposal) => {

0 commit comments

Comments
 (0)