From da21bdc1c1a4e7201bf4e8e6f27e49636d941fbf Mon Sep 17 00:00:00 2001 From: DanGould Date: Mon, 8 Jul 2024 16:56:53 -0400 Subject: [PATCH] Test non-incentivizing sweep payjoin Such one-output transactions pay the base costs to transact and provide an incentive for a receiver to contribute with reduced marginal cost of adding an input rather than an explicit change output. Allowing payjoin sweeps breaks common input heuristic for them. Sweeps with many inputs are common, making ~5.2% or ~54M of ~1.037B monetary, non-coinbase txs. Source monetary non-coinbase transactions count: https://blockchair.com/bitcoin/transactions?q=is_coinbase(false)#f=hash,is_coinbase,time Source sweep transactions count: https://blockchair.com/bitcoin/transactions?s=time(desc)&q=is_coinbase(false),input_count(2..),output_count(1)#f=hash,time --- payjoin/src/send/mod.rs | 5 +++++ payjoin/tests/integration.rs | 34 +++++++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/payjoin/src/send/mod.rs b/payjoin/src/send/mod.rs index 1f247bfc..2da0b4d2 100644 --- a/payjoin/src/send/mod.rs +++ b/payjoin/src/send/mod.rs @@ -62,6 +62,11 @@ pub struct RequestBuilder<'a> { uri: PjUri<'a>, disable_output_substitution: bool, fee_contribution: Option<(bitcoin::Amount, Option)>, + /// Decreases the fee contribution instead of erroring. + /// + /// If this option is true and a transaction with change amount lower than fee + /// contribution is provided then instead of returning error the fee contribution will + /// be just lowered in the request to match the change amount. clamp_fee_contribution: bool, min_fee_rate: FeeRate, } diff --git a/payjoin/tests/integration.rs b/payjoin/tests/integration.rs index 4397816d..be233279 100644 --- a/payjoin/tests/integration.rs +++ b/payjoin/tests/integration.rs @@ -402,14 +402,9 @@ mod integration { .assume_checked() .check_pj_supported() .unwrap(); - let psbt = build_original_psbt(&sender, &pj_uri)?; + let psbt = build_sweep_psbt(&sender, &pj_uri)?; let mut req_ctx = RequestBuilder::from_psbt_and_uri(psbt.clone(), pj_uri.clone())? - .build_with_additional_fee( - Amount::from_sat(10000), - None, - FeeRate::ZERO, - false, - )?; + .build_non_incentivizing()?; let (Request { url, body, .. }, send_ctx) = req_ctx.extract_v2(directory.to_owned())?; let response = agent @@ -826,6 +821,31 @@ mod integration { Ok(Psbt::from_str(&psbt)?) } + fn build_sweep_psbt( + sender: &bitcoincore_rpc::Client, + pj_uri: &PjUri, + ) -> Result { + let mut outputs = HashMap::with_capacity(1); + outputs.insert(pj_uri.address.to_string(), Amount::from_btc(50.0)?); + let options = bitcoincore_rpc::json::WalletCreateFundedPsbtOptions { + lock_unspent: Some(true), + fee_rate: Some(Amount::from_sat(2000)), + subtract_fee_from_outputs: vec![0], + ..Default::default() + }; + let psbt = sender + .wallet_create_funded_psbt( + &[], // inputs + &outputs, + None, // locktime + Some(options), + Some(true), // check that the sender properly clears keypaths + )? + .psbt; + let psbt = sender.wallet_process_psbt(&psbt, None, None, None)?.psbt; + Ok(Psbt::from_str(&psbt)?) + } + fn extract_pj_tx( sender: &bitcoincore_rpc::Client, psbt: Psbt,