@@ -1228,32 +1228,41 @@ pub struct Monitor {
12281228/// Call [`Receiver<Monitor>::check_payment`] to confirm the broadcast and conclude the Payjoin
12291229/// session.
12301230impl Receiver < Monitor > {
1231- /// Checks if the Payjoin proposal, the fallback transaction, or a double-spend attempt
1232- /// has been broadcasted by the sender. If the sender broadcasted either the Payjoin proposal
1233- /// or the fallback transaction, concludes the Payjoin session with a success. If there was a
1234- /// double-spend attempt, concludes with a failure.
1231+ /// Checks if the Payjoin proposal or the fallback transaction, has been broadcasted by the sender.
1232+ /// If the sender broadcasted either the Payjoin proposal or the fallback transaction, concludes
1233+ /// the Payjoin session with a success.
12351234 ///
1236- /// After the receiver has finalized the Payjoin proposal and sent it to the sender for the
1237- /// final signature and broadcast, what the sender does changes how the receiver should track
1238- /// the network and confirm that Payjoin session has concluded:
1239- ///
1240- /// 1. The sender may contribute segwit inputs, which would keep the transaction ID the same as
1241- /// what it was when the receiver sent the Payjoin proposal. In this case, the
1242- /// `transaction_exists` function will be used to confirm the broadcast.
1243- /// 2. The sender may contribute non-segwit inputs, which would change the
1244- /// transaction ID. In this case, `outpoint_spent` will be used to confirm that the UTXOs
1245- /// the receiver contributed with have been spent. This function will fail if UTXOs have
1246- /// been spent but not in the Payjoin proposal, signalling a double-spend.
1247- /// 3. The sender might not broadcast the Payjoin transaction and instead broadcast the original
1248- /// proposal which paid to the receiver but did not have any receiver contributions.
1235+ /// If the receiver input address type in the fallback transaction is non-SegWit, then this
1236+ /// function will directly conclude the Payjoin session with a Success without running the
1237+ /// provided `transaction_exists` closure. `transaction_exists` uses the transaction ID to
1238+ /// search for the transaction in the network. Since a non-SegWit input signature is going to
1239+ /// change the TXID of the Payjoin proposal, it cannot be monitored.
12491240 pub fn check_payment (
12501241 & self ,
12511242 transaction_exists : impl Fn ( Txid ) -> Result < Option < bitcoin:: Transaction > , ImplementationError > ,
1252- outpoint_spent : impl Fn ( OutPoint ) -> Result < bool , ImplementationError > ,
12531243 ) -> MaybeFatalOrSuccessTransition < SessionEvent , Self , Error > {
1244+ let fallback_tx = self
1245+ . state
1246+ . psbt_context
1247+ . original_psbt
1248+ . clone ( )
1249+ . extract_tx_fee_rate_limit ( )
1250+ . expect ( "fallback transaction should be in the receiver context" ) ;
1251+
1252+ // If the fallback transaction included any non-SegWit inputs, then the transaction ID of
1253+ // the Payjoin proposal is going to change when the sender signs their non-SegWit address
1254+ // one more time. The receiver cannot monitor the broadcast, and should conclude the session.
1255+ if fallback_tx. input . iter ( ) . any ( |txin| txin. witness . is_empty ( ) ) {
1256+ return MaybeFatalOrSuccessTransition :: success ( SessionEvent :: Closed (
1257+ SessionOutcome :: PayjoinProposalSent ,
1258+ ) ) ;
1259+ }
1260+
12541261 let payjoin_proposal = & self . state . psbt_context . payjoin_psbt ;
12551262 let payjoin_txid = payjoin_proposal. unsigned_tx . compute_txid ( ) ;
1256- // If we have a payjoin transaction with segwit inputs, we can check for the txid
1263+ // If the sender is spending SegWit-only inputs, then the transaction ID of the Payjoin proposal
1264+ // is not going to change when the sender signs it. So we can use the TXID to determine if
1265+ // the Payjoin proposal has been broadcasted.
12571266 match transaction_exists ( payjoin_txid) {
12581267 Ok ( Some ( tx) ) => {
12591268 let tx_id = tx. compute_txid ( ) ;
@@ -1270,7 +1279,7 @@ impl Receiver<Monitor> {
12701279 tx. input . get ( i) . expect ( "sender_input_indexes should return valid indices" ) ;
12711280 sender_witnesses. push ( ( input. script_sig . clone ( ) , input. witness . clone ( ) ) ) ;
12721281 }
1273- // Payjoin transaction with segwit inputs was detected. Log the signatures and complete the session
1282+ // Payjoin transaction with SegWit inputs was detected. Log the signatures and complete the session.
12741283 return MaybeFatalOrSuccessTransition :: success ( SessionEvent :: Closed (
12751284 SessionOutcome :: Success ( sender_witnesses) ,
12761285 ) ) ;
@@ -1279,14 +1288,8 @@ impl Receiver<Monitor> {
12791288 Err ( e) => return MaybeFatalOrSuccessTransition :: transient ( Error :: Implementation ( e) ) ,
12801289 }
12811290
1282- // Check for fallback being broadcasted
1283- let fallback_tx = self
1284- . state
1285- . psbt_context
1286- . original_psbt
1287- . clone ( )
1288- . extract_tx_fee_rate_limit ( )
1289- . expect ( "Checked in earlier typestates" ) ;
1291+ // If the Payjoin proposal was not found, check the fallback transaction, at it is
1292+ // the second of two transactions whose IDs the receiver is aware of.
12901293 match transaction_exists ( fallback_tx. compute_txid ( ) ) {
12911294 Ok ( Some ( _) ) =>
12921295 return MaybeFatalOrSuccessTransition :: success ( SessionEvent :: Closed (
@@ -1296,29 +1299,6 @@ impl Receiver<Monitor> {
12961299 Err ( e) => return MaybeFatalOrSuccessTransition :: transient ( Error :: Implementation ( e) ) ,
12971300 }
12981301
1299- let mut outpoints_spend = 0 ;
1300- for ot in payjoin_proposal. unsigned_tx . input . iter ( ) {
1301- match outpoint_spent ( ot. previous_output ) {
1302- Ok ( false ) => { }
1303- Ok ( true ) => outpoints_spend += 1 ,
1304- Err ( e) =>
1305- return MaybeFatalOrSuccessTransition :: transient ( Error :: Implementation ( e) ) ,
1306- }
1307- }
1308-
1309- if outpoints_spend == payjoin_proposal. unsigned_tx . input . len ( ) {
1310- // All the payjoin proposal outpoints were spent. This means our payjoin proposal has non-segwit inputs and is broadcasted.
1311- return MaybeFatalOrSuccessTransition :: success ( SessionEvent :: Closed (
1312- // TODO: there seems to be not great way to get the tx of the tx that spent these outpoints.
1313- SessionOutcome :: Success ( vec ! [ ] ) ,
1314- ) ) ;
1315- } else if outpoints_spend > 0 {
1316- // Some outpoints were spent but not in the payjoin proposal. This is a double spend.
1317- return MaybeFatalOrSuccessTransition :: success ( SessionEvent :: Closed (
1318- SessionOutcome :: Failure ,
1319- ) ) ;
1320- }
1321-
13221302 MaybeFatalOrSuccessTransition :: no_results ( self . clone ( ) )
13231303 }
13241304}
@@ -1359,7 +1339,7 @@ pub(crate) fn pj_uri<'a>(
13591339pub mod test {
13601340 use std:: str:: FromStr ;
13611341
1362- use bitcoin:: FeeRate ;
1342+ use bitcoin:: { FeeRate , ScriptBuf , Witness } ;
13631343 use once_cell:: sync:: Lazy ;
13641344 use payjoin_test_utils:: {
13651345 BoxError , EXAMPLE_URL , KEM , KEY_ID , ORIGINAL_PSBT , PARSED_ORIGINAL_PSBT ,
@@ -1444,7 +1424,7 @@ pub mod test {
14441424 // Nothing was spent, should be in the same state
14451425 let persister = InMemoryTestPersister :: default ( ) ;
14461426 let res = monitor
1447- . check_payment ( |_| Ok ( None ) , |_| Ok ( false ) )
1427+ . check_payment ( |_| Ok ( None ) )
14481428 . save ( & persister)
14491429 . expect ( "InMemoryTestPersister shouldn't fail" ) ;
14501430 assert ! ( matches!( res, OptionalTransitionOutcome :: Stasis ( _) ) ) ;
@@ -1454,82 +1434,73 @@ pub mod test {
14541434 // Payjoin was broadcasted, should progress to success
14551435 let persister = InMemoryTestPersister :: default ( ) ;
14561436 let res = monitor
1457- . check_payment ( |_| Ok ( Some ( payjoin_tx. clone ( ) ) ) , |_| Ok ( false ) )
1437+ . check_payment ( |_| Ok ( Some ( payjoin_tx. clone ( ) ) ) )
14581438 . save ( & persister)
14591439 . expect ( "InMemoryTestPersister shouldn't fail" ) ;
14601440
14611441 assert ! ( matches!( res, OptionalTransitionOutcome :: Progress ( _) ) ) ;
14621442 assert ! ( persister. inner. read( ) . expect( "Shouldn't be poisoned" ) . is_closed) ;
14631443 assert_eq ! ( persister. inner. read( ) . expect( "Shouldn't be poisoned" ) . events. len( ) , 1 ) ;
1464- // TODO: check for exact events
1444+ assert_eq ! (
1445+ persister. inner. read( ) . expect( "Shouldn't be poisoned" ) . events. last( ) ,
1446+ Some ( & SessionEvent :: Closed ( SessionOutcome :: Success ( vec![ (
1447+ ScriptBuf :: default ( ) ,
1448+ Witness :: default ( )
1449+ ) ] ) ) )
1450+ ) ;
14651451
1466- // fallback was broadcasted, should progress to success
1452+ // Fallback was broadcasted, should progress to success
14671453 let persister = InMemoryTestPersister :: default ( ) ;
14681454 let res = monitor
1469- . check_payment (
1470- |txid| {
1471- // Emulate if one of the fallback outpoints was double spent
1472- if txid == original_tx. compute_txid ( ) {
1473- Ok ( Some ( original_tx. clone ( ) ) )
1474- } else {
1475- Ok ( None )
1476- }
1477- } ,
1478- |_| Ok ( false ) ,
1479- )
1455+ . check_payment ( |txid| {
1456+ // Emulate if one of the fallback outpoints was double spent
1457+ if txid == original_tx. compute_txid ( ) {
1458+ Ok ( Some ( original_tx. clone ( ) ) )
1459+ } else {
1460+ Ok ( None )
1461+ }
1462+ } )
14801463 . save ( & persister)
14811464 . expect ( "InMemoryTestPersister shouldn't fail" ) ;
14821465
14831466 assert ! ( matches!( res, OptionalTransitionOutcome :: Progress ( _) ) ) ;
14841467 assert ! ( persister. inner. read( ) . expect( "Shouldn't be poisoned" ) . is_closed) ;
1468+ assert_eq ! ( persister. inner. read( ) . expect( "Shouldn't be poisoned" ) . events. len( ) , 1 ) ;
14851469 assert_eq ! (
14861470 persister. inner. read( ) . expect( "Shouldn't be poisoned" ) . events. last( ) ,
14871471 Some ( & SessionEvent :: Closed ( SessionOutcome :: FallbackBroadcasted ) )
14881472 ) ;
14891473
1490- let persister = InMemoryTestPersister :: default ( ) ;
1491- let res = monitor
1492- . check_payment ( |_| Ok ( None ) , |_| Ok ( true ) )
1493- . save ( & persister)
1494- . expect ( "InMemoryTestPersister shouldn't fail" ) ;
1474+ // Fallback transaction is non-SegWit address type, should end the session without checking
1475+ // the network for broadcasts.
1476+ // Not using the test-utils vectors here as they are SegWit.
1477+ let parsed_original_psbt_p2pkh = Psbt :: from_str ( "cHNidP8BAFICAAAAAd5tU7sqAGa46oUVdEfV1HTeVVPYqvSvxy8/dvF3dwpZAQAAAAD9////AUTxBSoBAAAAFgAUhV1NWa6seBB5g6VZC2lnduxfEaUAAAAAAAEA/QoBAgAAAAIT2eO393FPqJ4fw6NH0rXALebtTCderecX0y6DumtjNgAAAAAA/f///5hrwcRiTXqXScbvk3APDdzy162Yj+6JD/iSEO9KYQl+AQAAAGpHMEQCIGcFm57xH5tQvJMipWfzxS7OGRi7+JfTT6WA27kOt8fVAiAp2I3WGdLk3/dVhoVxN6Jl9Wp/xeCIZZ1OTukSs8jszgEhAjjEq9kNnhvQbdVlWsE9QTIe4h39UPQ8flvU5Ivq6DFm/f///wIo3gUqAQAAABl2qRTWng6zTFWPZX1k12UqqBI6kLz8z4isAPIFKgEAAAAZdqkUIz2wzl605b3cg3j72nXReQuXXaWIrGcAAAABB2pHMEQCIEP33+9X/ecNmaiydM54HS+HoHfZygAQ/vMlc5r1IWkeAiA9oKjOVmp+RnrDF4zzHHGtoG1yy1+UWXBNaDiwd0LokgEhAmfCwbIv1mi5psiB3HFqXN1bFAo+goNUPWIso60J1matAAA=" ) . expect ( "known psbt should parse" ) ;
1478+ let parsed_payjoin_proposal_p2pkh: Psbt =
1479+ Psbt :: from_str ( "cHNidP8BAHsCAAAAAphrwcRiTXqXScbvk3APDdzy162Yj+6JD/iSEO9KYQl+AAAAAAD9////3m1TuyoAZrjqhRV0R9XUdN5VU9iq9K/HLz928Xd3ClkBAAAAAP3///8BsOILVAIAAAAWABSFXU1Zrqx4EHmDpVkLaWd27F8RpQAAAAAAAQCgAgAAAAJgEjBIihNzFXar4wIYepzXJwQVpbqZep9GCY8pQCqh3wAAAAAA/f///x8caN/onT7AOPRWJz7vnT6yiNxcsAIs/U3RcgU4kiq4AAAAAAD9////AgDyBSoBAAAAGXapFDGh2kOIa5aNVHT2bHSoFfcawEMiiKyk6QUqAQAAABl2qRQY8AsQvx+jg9NdGUwCuShS3qk2KYisZwAAAAEBIgDyBSoBAAAAGXapFDGh2kOIa5aNVHT2bHSoFfcawEMiiKwBB2pHMEQCICQEE2dMDzlyH3ojsc0l98Da0yd2ARuy5AcWQjlgHHjkAiA70WPB+yQhW5zhsOBTg6qLsi0KzoofRAj1BZFpKT2QwAEhA68L99Q+xdIIp0rinuVDs+4qmqMZwg4E+aqbTQ8RClXLAAEA/QoBAgAAAAIT2eO393FPqJ4fw6NH0rXALebtTCderecX0y6DumtjNgAAAAAA/f///5hrwcRiTXqXScbvk3APDdzy162Yj+6JD/iSEO9KYQl+AQAAAGpHMEQCIGcFm57xH5tQvJMipWfzxS7OGRi7+JfTT6WA27kOt8fVAiAp2I3WGdLk3/dVhoVxN6Jl9Wp/xeCIZZ1OTukSs8jszgEhAjjEq9kNnhvQbdVlWsE9QTIe4h39UPQ8flvU5Ivq6DFm/f///wIo3gUqAQAAABl2qRTWng6zTFWPZX1k12UqqBI6kLz8z4isAPIFKgEAAAAZdqkUIz2wzl605b3cg3j72nXReQuXXaWIrGcAAAAAAA==" ) . expect ( "known psbt should parse" ) ;
14951480
1496- assert ! ( matches!( res, OptionalTransitionOutcome :: Progress ( _) ) ) ;
1497- assert ! ( persister. inner. read( ) . expect( "Shouldn't be poisoned" ) . is_closed) ;
1498- assert_eq ! ( persister. inner. read( ) . expect( "Shouldn't be poisoned" ) . events. len( ) , 1 ) ;
1481+ let psbt_ctx_p2pkh = PsbtContext {
1482+ original_psbt : parsed_original_psbt_p2pkh. clone ( ) ,
1483+ payjoin_psbt : parsed_payjoin_proposal_p2pkh. clone ( ) ,
1484+ } ;
1485+ let monitor = Receiver {
1486+ state : Monitor { psbt_context : psbt_ctx_p2pkh } ,
1487+ session_context : SHARED_CONTEXT . clone ( ) ,
1488+ } ;
14991489
15001490 let persister = InMemoryTestPersister :: default ( ) ;
1501- monitor
1502- . check_payment (
1503- |_| Ok ( None ) ,
1504- |outpoint| {
1505- if outpoint == payjoin_tx. input [ 0 ] . previous_output {
1506- Ok ( true )
1507- } else {
1508- Ok ( false )
1509- }
1510- } ,
1511- )
1491+ let res = monitor
1492+ . check_payment ( |_| panic ! ( "check_payment should return before this closure is called" ) )
15121493 . save ( & persister)
15131494 . expect ( "InMemoryTestPersister shouldn't fail" ) ;
15141495
1496+ assert ! ( matches!( res, OptionalTransitionOutcome :: Progress ( _) ) ) ;
15151497 assert ! ( persister. inner. read( ) . expect( "Shouldn't be poisoned" ) . is_closed) ;
15161498 assert_eq ! ( persister. inner. read( ) . expect( "Shouldn't be poisoned" ) . events. len( ) , 1 ) ;
15171499 assert_eq ! (
15181500 persister. inner. read( ) . expect( "Shouldn't be poisoned" ) . events. last( ) ,
1519- Some ( & SessionEvent :: Closed ( SessionOutcome :: Failure ) )
1501+ Some ( & SessionEvent :: Closed ( SessionOutcome :: PayjoinProposalSent ) )
15201502 ) ;
15211503
1522- // assert_eq!(
1523- // err.to_string(),
1524- // Error::Protocol(ProtocolError::V2(
1525- // InternalSessionError::FallbackOutpointsSpent(vec![
1526- // payjoin_tx.input[0].previous_output
1527- // ],)
1528- // .into()
1529- // ))
1530- // .to_string()
1531- // );
1532-
15331504 Ok ( ( ) )
15341505 }
15351506
0 commit comments