diff --git a/payjoin-cli/src/app.rs b/payjoin-cli/src/app.rs index 25389696..5a592088 100644 --- a/payjoin-cli/src/app.rs +++ b/payjoin-cli/src/app.rs @@ -10,7 +10,7 @@ use bitcoincore_rpc::RpcApi; use clap::ArgMatches; use config::{Config, File, FileFormat}; use payjoin::bitcoin::psbt::Psbt; -use payjoin::receive::{Error, PayjoinProposal, ProvisionalProposal}; +use payjoin::receive::{Error, ProvisionalProposal}; use payjoin::{bitcoin, PjUriExt, UriExt}; use rouille::{Request, Response}; use serde::{Deserialize, Serialize}; @@ -296,7 +296,7 @@ impl App { })?; log::trace!("check4"); - let mut payjoin = payjoin.identify_receiver_outputs(|output_script| { + let mut provisional_payjoin = payjoin.identify_receiver_outputs(|output_script| { if let Ok(address) = bitcoin::Address::from_script(output_script, network) { self.bitcoind .get_address_info(&address) @@ -309,7 +309,7 @@ impl App { if !self.config.sub_only { // Select receiver payjoin inputs. - _ = try_contributing_inputs(&mut payjoin, &self.bitcoind) + _ = try_contributing_inputs(&mut provisional_payjoin, &self.bitcoind) .map_err(|e| log::warn!("Failed to contribute inputs: {}", e)); } @@ -318,24 +318,23 @@ impl App { .get_new_address(None, None) .map_err(|e| Error::Server(e.into()))? .assume_checked(); - payjoin.substitute_output_address(receiver_substitute_address); + provisional_payjoin.substitute_output_address(receiver_substitute_address); - let payjoin_proposal_psbt = payjoin.apply_fee(Some(1))?; - - log::debug!("Extracted PSBT: {:#?}", payjoin_proposal_psbt); - // Sign payjoin psbt - let payjoin_base64_string = base64::encode(&payjoin_proposal_psbt.serialize()); - // `wallet_process_psbt` adds available utxo data and finalizes - let payjoin_proposal_psbt = self - .bitcoind - .wallet_process_psbt(&payjoin_base64_string, None, None, Some(false)) - .map_err(|e| Error::Server(e.into()))? - .psbt; - let payjoin_proposal_psbt = Psbt::from_str(&payjoin_proposal_psbt) - .context("Failed to parse PSBT") - .map_err(|e| Error::Server(e.into()))?; - let payjoin_proposal = payjoin.prepare_psbt(payjoin_proposal_psbt)?; - let payjoin_proposal_psbt = payjoin_proposal.get_payjoin_psbt(); + let payjoi_proposal = provisional_payjoin.finalize_proposal( + |psbt: &Psbt| { + self.bitcoind + .wallet_process_psbt( + &bitcoin::base64::encode(psbt.serialize()), + None, + None, + Some(false), + ) + .map(|res| Psbt::from_str(&res.psbt).map_err(|e| Error::Server(e.into()))) + .map_err(|e| Error::Server(e.into()))? + }, + Some(bitcoin::FeeRate::MIN), + )?; + let payjoin_proposal_psbt = payjoi_proposal.psbt(); log::debug!("Receiver's Payjoin proposal PSBT Rsponse: {:#?}", payjoin_proposal_psbt); let payload = base64::encode(&payjoin_proposal_psbt.serialize()); diff --git a/payjoin/Cargo.toml b/payjoin/Cargo.toml index 931aea4e..fe62c687 100644 --- a/payjoin/Cargo.toml +++ b/payjoin/Cargo.toml @@ -22,10 +22,10 @@ bip21 = "0.3.1" log = { version = "0.4.14"} rand = { version = "0.8.4", optional = true } url = "2.2.2" -bitcoind = { version = "0.31.1", features = ["0_21_2"] } [dev-dependencies] env_logger = "0.9.0" +bitcoind = { version = "0.31.1", features = ["0_21_2"] } [package.metadata.docs.rs] features = ["send", "receive"] diff --git a/payjoin/src/receive/error.rs b/payjoin/src/receive/error.rs index 3171602c..ffc91360 100644 --- a/payjoin/src/receive/error.rs +++ b/payjoin/src/receive/error.rs @@ -41,9 +41,7 @@ pub struct RequestError(InternalRequestError); #[derive(Debug)] pub(crate) enum InternalRequestError { Psbt(bitcoin::psbt::Error), - PsbtParseError(bitcoin::psbt::PsbtParseError), Base64(bitcoin::base64::DecodeError), - BitcoinCoreRpc(bitcoind::bitcoincore_rpc::Error), Io(std::io::Error), MissingHeader(&'static str), InvalidContentType(String), @@ -80,8 +78,6 @@ impl fmt::Display for RequestError { } match &self.0 { - InternalRequestError::PsbtParseError(e) => write_error(f, "psbt-parse-error", e), - InternalRequestError::BitcoinCoreRpc(e) => write_error(f, "bitcoin-core-rpc-error", e), InternalRequestError::Psbt(e) => write_error(f, "psbt-error", e), InternalRequestError::Base64(e) => write_error(f, "base64-decode-error", e), InternalRequestError::Io(e) => write_error(f, "io-error", e), diff --git a/payjoin/src/receive/mod.rs b/payjoin/src/receive/mod.rs index 81c32214..b9c9a0ea 100644 --- a/payjoin/src/receive/mod.rs +++ b/payjoin/src/receive/mod.rs @@ -276,8 +276,6 @@ use bitcoin::{Amount, FeeRate, OutPoint, Script, TxOut}; mod error; mod optional_parameters; -use bitcoind::bitcoincore_rpc; -use bitcoind::bitcoincore_rpc::core_rpc_json::WalletProcessPsbtResult; pub use error::{Error, RequestError, SelectionError}; use error::{InternalRequestError, InternalSelectionError}; use optional_parameters::Params; @@ -553,7 +551,6 @@ impl OutputsUnknown { } /// A mutable checked proposal that the receiver may contribute inputs to to make a payjoin. - pub struct PayjoinProposal { payjoin_psbt: Psbt, params: Params, @@ -569,9 +566,9 @@ impl PayjoinProposal { self.params.disable_output_substitution } - pub fn get_owned_vouts(&self) -> Vec { self.owned_vouts.clone() } + pub fn get_owned_vouts(&self) -> &Vec { &self.owned_vouts } - pub fn get_payjoin_psbt(&self) -> Psbt { self.payjoin_psbt.clone() } + pub fn psbt(&self) -> &Psbt { &self.payjoin_psbt } } /// A mutable checked proposal that the receiver may contribute inputs to to make a payjoin. @@ -724,12 +721,11 @@ impl ProvisionalProposal { /// this is kind of a "build_proposal" step before we sign and finalize and extract /// /// WARNING: DO NOT ALTER INPUTS OR OUTPUTS AFTER THIS STEP - pub fn apply_fee( + fn apply_fee( &mut self, - min_feerate_sat_per_vb: Option, + min_feerate_sat_per_vb: Option, ) -> Result<&Psbt, RequestError> { - let min_feerate = - FeeRate::from_sat_per_vb_unchecked(min_feerate_sat_per_vb.unwrap_or_default()); + let min_feerate = min_feerate_sat_per_vb.unwrap_or(FeeRate::MIN); log::trace!("min_feerate: {:?}", min_feerate); log::trace!("params.min_feerate: {:?}", self.params.min_feerate); let min_feerate = max(min_feerate, self.params.min_feerate); @@ -777,7 +773,7 @@ impl ProvisionalProposal { /// and return a PSBT that can produce a consensus-valid transaction that the sender will accept. /// /// wallet_process_psbt should sign and finalize receiver inputs - pub fn prepare_psbt(mut self, processed_psbt: Psbt) -> Result { + fn prepare_psbt(mut self, processed_psbt: Psbt) -> Result { self.payjoin_psbt = processed_psbt; log::trace!("Preparing PSBT {:#?}", self.payjoin_psbt); for input in self.payjoin_psbt.inputs_mut() { @@ -817,24 +813,15 @@ impl ProvisionalProposal { pub fn finalize_proposal( mut self, - wallet_process_psbt: impl Fn(&str) -> Result, - min_feerate_sat_per_vb: Option, - ) -> Result { - let payjoin_proposal_psbt: &Psbt = self.apply_fee(min_feerate_sat_per_vb)?; - let payjoin_base64_string = bitcoin::base64::encode(&payjoin_proposal_psbt.serialize()); - let payjoin_proposal_psbt = match wallet_process_psbt(&payjoin_base64_string) { - Ok(p) => p.psbt, - Err(e) => return Err(RequestError::from(InternalRequestError::BitcoinCoreRpc(e))), - }; - let payjoin_proposal_psbt = match Psbt::from_str(&payjoin_proposal_psbt) { - Ok(p) => p, - Err(e) => return Err(RequestError::from(InternalRequestError::PsbtParseError(e))), - }; - - let payjoin_proposal = match self.prepare_psbt(payjoin_proposal_psbt) { - Ok(p) => p, - Err(e) => return Err(RequestError::from(e)), - }; + wallet_process_psbt: impl Fn(&Psbt) -> Result, + min_feerate_sat_per_vb: Option, + ) -> Result { + let psbt = self.apply_fee(min_feerate_sat_per_vb)?; + let psbt = wallet_process_psbt(psbt).map_err(|e| { + log::error!("wallet_process_psbt error"); + Error::from(e) + })?; + let payjoin_proposal = self.prepare_psbt(psbt).map_err(RequestError::from)?; Ok(payjoin_proposal) } } diff --git a/payjoin/tests/integration.rs b/payjoin/tests/integration.rs index 0f63bedc..a1a5cfcd 100644 --- a/payjoin/tests/integration.rs +++ b/payjoin/tests/integration.rs @@ -6,13 +6,13 @@ mod integration { use bitcoin::psbt::Psbt; use bitcoin::{Amount, OutPoint}; use bitcoind::bitcoincore_rpc; - use bitcoind::bitcoincore_rpc::core_rpc_json::AddressType; + use bitcoind::bitcoincore_rpc::core_rpc_json::{AddressType, WalletProcessPsbtResult}; use bitcoind::bitcoincore_rpc::RpcApi; use log::{debug, log_enabled, Level}; use payjoin::bitcoin::base64; use payjoin::receive::Headers; use payjoin::send::Request; - use payjoin::{bitcoin, PjUriExt, Uri, UriExt}; + use payjoin::{bitcoin, Error, PjUriExt, Uri, UriExt}; #[test] fn integration_test() { @@ -200,20 +200,27 @@ mod integration { let receiver_substitute_address = receiver.get_new_address(None, None).unwrap().assume_checked(); payjoin.substitute_output_address(receiver_substitute_address); - - let payjoin_proposal_psbt = payjoin.apply_fee(None).unwrap(); - - // Sign payjoin psbt - let payjoin_base64_string = base64::encode(&payjoin_proposal_psbt.serialize()); - let payjoin_proposal_psbt = receiver - .wallet_process_psbt(&payjoin_base64_string, None, None, Some(false)) - .unwrap() - .psbt; - let payjoin_proposal_psbt = Psbt::from_str(&payjoin_proposal_psbt).unwrap(); - - let payjoin_proposal_psbt = payjoin.prepare_psbt(payjoin_proposal_psbt).unwrap(); - debug!("Receiver's Payjoin proposal PSBT: {:#?}", payjoin_proposal_psbt.get_payjoin_psbt()); - - base64::encode(&payjoin_proposal_psbt.get_payjoin_psbt().serialize()) + let payjoin_proposal = payjoin + .finalize_proposal( + |psbt: &Psbt| { + Ok(receiver + .wallet_process_psbt( + &bitcoin::base64::encode(psbt.serialize()), + None, + None, + Some(false), + ) + .map(|res: WalletProcessPsbtResult| { + let psbt = Psbt::from_str(&res.psbt).unwrap(); + return psbt; + }) + .unwrap()) + }, + Some(bitcoin::FeeRate::MIN), + ) + .unwrap(); + let psbt = payjoin_proposal.psbt(); + debug!("Receiver's Payjoin proposal PSBT: {:#?}", &psbt); + base64::encode(&psbt.serialize()) } }