Skip to content

Commit 9eaebe1

Browse files
Update payjoin-cli to non-blocking receive interface
Migrate both v1 and v2 receiver flows in payjoin-cli from the callback-based validation API to the two-phase extract/apply non-blocking API.
1 parent 258938d commit 9eaebe1

2 files changed

Lines changed: 65 additions & 62 deletions

File tree

payjoin-cli/src/app/v1.rs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use hyper_util::rt::TokioIo;
1313
use payjoin::bitcoin::consensus::encode::serialize_hex;
1414
use payjoin::bitcoin::{Amount, FeeRate};
1515
use payjoin::receive::v1::{PayjoinProposal, UncheckedOriginalPayload};
16-
use payjoin::receive::Error;
16+
use payjoin::receive::{check_references, Error};
1717
use payjoin::send::v1::SenderBuilder;
1818
use payjoin::{ImplementationError, IntoUrl, Uri, UriExt};
1919
use tokio::net::TcpListener;
@@ -347,33 +347,37 @@ impl App {
347347
let wallet = self.wallet();
348348

349349
// Receive Check 1: Can Broadcast
350-
let proposal = proposal.check_broadcast_suitability(None, |tx| {
351-
wallet
352-
.can_broadcast(tx)
353-
.map_err(|e| ImplementationError::from(e.into_boxed_dyn_error()))
354-
})?;
350+
let is_broadcast_suitable = wallet
351+
.can_broadcast(&proposal.extract_tx_to_check_broadcast_suitability())
352+
.map_err(|e| ImplementationError::from(e.into_boxed_dyn_error()))?;
353+
let proposal = proposal.apply_broadcast_suitability(None, is_broadcast_suitable)?;
355354
tracing::trace!("check1");
356355

357356
// in a payment processor where the sender could go offline, this is where you schedule to broadcast the original_tx
358357
let _to_broadcast_in_failure_case = proposal.extract_tx_to_schedule_broadcast();
359358

360359
// Receive Check 2: receiver can't sign for proposal inputs
361-
let proposal = proposal.check_inputs_not_owned(&mut |input| {
360+
let refs = proposal.get_input_script_refs()?;
361+
let checked_refs = check_references(refs, &mut |input| {
362362
wallet.is_mine(input).map_err(|e| ImplementationError::from(e.into_boxed_dyn_error()))
363363
})?;
364+
let proposal = proposal.apply_input_owned_checks(checked_refs)?;
364365
tracing::trace!("check2");
365366

366367
// Receive Check 3: have we seen this input before? More of a check for non-interactive i.e. payment processor receivers.
367-
let payjoin = proposal.check_no_inputs_seen_before(&mut |input| {
368-
Ok(self.db.insert_input_seen_before(*input)?)
369-
})?;
368+
let refs = proposal.get_input_outpoint_refs();
369+
let checked_refs =
370+
check_references(refs, &mut |input| Ok(self.db.insert_input_seen_before(*input)?))?;
371+
let payjoin = proposal.apply_input_seen_checks(checked_refs)?;
370372
tracing::trace!("check3");
371373

372-
let payjoin = payjoin.identify_receiver_outputs(&mut |output_script| {
374+
let refs = payjoin.get_output_script_refs();
375+
let checked_refs = check_references(refs, &mut |output_script| {
373376
wallet
374377
.is_mine(output_script)
375378
.map_err(|e| ImplementationError::from(e.into_boxed_dyn_error()))
376379
})?;
380+
let payjoin = payjoin.apply_output_owned_checks(checked_refs)?;
377381

378382
let payjoin = payjoin
379383
.substitute_receiver_script(

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

Lines changed: 50 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use anyhow::{anyhow, Context, Result};
55
use payjoin::bitcoin::consensus::encode::serialize_hex;
66
use payjoin::bitcoin::{Amount, FeeRate};
77
use payjoin::persist::{OptionalTransitionOutcome, SessionPersister};
8+
use payjoin::receive::check_references;
89
use payjoin::receive::v2::{
910
replay_event_log as replay_receiver_event_log, HasReplyableError, Initialized,
1011
MaybeInputsOwned, MaybeInputsSeen, Monitor, OutputsUnknown, PayjoinProposal,
@@ -702,13 +703,11 @@ impl App {
702703
persister: &ReceiverPersister,
703704
) -> Result<()> {
704705
let wallet = self.wallet();
705-
let proposal = proposal
706-
.check_broadcast_suitability(None, |tx| {
707-
wallet
708-
.can_broadcast(tx)
709-
.map_err(|e| ImplementationError::from(e.into_boxed_dyn_error()))
710-
})
711-
.save(persister)?;
706+
let is_broadcast_suitable = wallet
707+
.can_broadcast(&proposal.extract_tx_to_check_broadcast_suitability())
708+
.map_err(|e| ImplementationError::from(e.into_boxed_dyn_error()))?;
709+
let proposal =
710+
proposal.apply_broadcast_suitability(None, is_broadcast_suitable).save(persister)?;
712711

713712
println!("Fallback transaction received. Consider broadcasting this to get paid if the Payjoin fails:");
714713
println!("{}", serialize_hex(&proposal.extract_tx_to_schedule_broadcast()));
@@ -721,13 +720,11 @@ impl App {
721720
persister: &ReceiverPersister,
722721
) -> Result<()> {
723722
let wallet = self.wallet();
724-
let proposal = proposal
725-
.check_inputs_not_owned(&mut |input| {
726-
wallet
727-
.is_mine(input)
728-
.map_err(|e| ImplementationError::from(e.into_boxed_dyn_error()))
729-
})
730-
.save(persister)?;
723+
let refs = proposal.get_input_script_refs()?;
724+
let checked_refs = check_references(refs, &mut |input| {
725+
wallet.is_mine(input).map_err(|e| ImplementationError::from(e.into_boxed_dyn_error()))
726+
})?;
727+
let proposal = proposal.apply_input_owned_checks(checked_refs).save(persister)?;
731728
self.check_no_inputs_seen_before(proposal, persister).await
732729
}
733730

@@ -736,11 +733,10 @@ impl App {
736733
proposal: Receiver<MaybeInputsSeen>,
737734
persister: &ReceiverPersister,
738735
) -> Result<()> {
739-
let proposal = proposal
740-
.check_no_inputs_seen_before(&mut |input| {
741-
Ok(self.db.insert_input_seen_before(*input)?)
742-
})
743-
.save(persister)?;
736+
let refs = proposal.get_input_outpoint_refs();
737+
let checked_refs =
738+
check_references(refs, &mut |input| Ok(self.db.insert_input_seen_before(*input)?))?;
739+
let proposal = proposal.apply_input_seen_checks(checked_refs).save(persister)?;
744740
self.identify_receiver_outputs(proposal, persister).await
745741
}
746742

@@ -750,13 +746,13 @@ impl App {
750746
persister: &ReceiverPersister,
751747
) -> Result<()> {
752748
let wallet = self.wallet();
753-
let proposal = proposal
754-
.identify_receiver_outputs(&mut |output_script| {
755-
wallet
756-
.is_mine(output_script)
757-
.map_err(|e| ImplementationError::from(e.into_boxed_dyn_error()))
758-
})
759-
.save(persister)?;
749+
let refs = proposal.get_output_script_refs();
750+
let checked_refs = check_references(refs, &mut |output_script| {
751+
wallet
752+
.is_mine(output_script)
753+
.map_err(|e| ImplementationError::from(e.into_boxed_dyn_error()))
754+
})?;
755+
let proposal = proposal.apply_output_owned_checks(checked_refs).save(persister)?;
760756
self.commit_outputs(proposal, persister).await
761757
}
762758

@@ -804,13 +800,11 @@ impl App {
804800
persister: &ReceiverPersister,
805801
) -> Result<()> {
806802
let wallet = self.wallet();
807-
let proposal = proposal
808-
.finalize_proposal(|psbt| {
809-
wallet
810-
.process_psbt(psbt)
811-
.map_err(|e| ImplementationError::from(e.into_boxed_dyn_error()))
812-
})
813-
.save(persister)?;
803+
let psbt = proposal.psbt_to_sign();
804+
let signed_psbt = wallet
805+
.process_psbt(&psbt)
806+
.map_err(|e| ImplementationError::from(e.into_boxed_dyn_error()))?;
807+
let proposal = proposal.finalize_signed_proposal(&signed_psbt).save(persister)?;
814808
self.send_payjoin_proposal(proposal, persister).await
815809
}
816810

@@ -847,27 +841,32 @@ impl App {
847841

848842
tracing::debug!("Polling for payment confirmation");
849843

844+
let fallback_txid = proposal.extract_fallback_txid();
845+
let payjoin_txid = proposal.extract_payjoin_proposal_txid();
846+
let get_raw_tx = |txid| {
847+
self.wallet()
848+
.get_raw_transaction(&txid)
849+
.map_err(|e| ImplementationError::from(e.into_boxed_dyn_error()))
850+
};
851+
match proposal.check_fallback_monitorable().save(persister)? {
852+
OptionalTransitionOutcome::Progress(_) => {
853+
println!("Unable to monitor for fallback tx containing non-segwit inputs, completing session");
854+
return Ok(());
855+
}
856+
OptionalTransitionOutcome::Stasis(_) => {}
857+
}
850858
let result = tokio::time::timeout(timeout_duration, async {
851859
loop {
852860
interval.tick().await;
853-
let check_result = proposal
854-
.check_payment(|txid| {
855-
self.wallet()
856-
.get_raw_transaction(&txid)
857-
.map_err(|e| ImplementationError::from(e.into_boxed_dyn_error()))
858-
})
859-
.save(persister);
860-
861-
match check_result {
862-
Ok(_) => {
863-
println!("Payjoin transaction detected in the mempool!");
864-
return Ok(());
865-
}
866-
Err(_) => {
867-
// keep polling
868-
869-
continue;
870-
}
861+
if let Some(tx) = get_raw_tx(payjoin_txid)? {
862+
proposal.payjoin_tx_exists(tx).save(persister)?;
863+
println!("Payjoin transaction detected in the mempool!");
864+
return Ok(());
865+
};
866+
if get_raw_tx(fallback_txid)?.is_some() {
867+
proposal.fallback_tx_exists().save(persister)?;
868+
println!("Fallback transaction detected in the mempool!");
869+
return Ok(());
871870
}
872871
}
873872
})

0 commit comments

Comments
 (0)