-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
427 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
use std::{str::FromStr, collections::HashMap}; | ||
|
||
use anyhow::{anyhow, Result}; | ||
use payjoin::{receive::{PayjoinProposal, UncheckedProposal, ProvisionalProposal}, Error}; | ||
use tracing::instrument; | ||
|
||
use super::error::*; | ||
use crate::primitives::bitcoin; | ||
|
||
const BOOTSTRAP_KEY_NAME: &str = "payjoin_bootstrap_key"; | ||
|
||
pub struct PayjoinApp { | ||
// config: bitcoind ledger, wallet, pj_host, pj_endpoint | ||
// ledger: Ledger, | ||
// network: bitcoin::Network, | ||
pool: sqlx::PgPool, | ||
} | ||
|
||
impl PayjoinApp { | ||
pub fn new(pool: sqlx::PgPool) -> Self { | ||
Self { | ||
pool, | ||
} | ||
} | ||
|
||
#[instrument(name = "payjoin_app.process_proposal", skip(self), err)] | ||
fn process_proposal(&self, proposal: UncheckedProposal) -> Result<PayjoinProposal, Error> { | ||
let bitcoind = self.bitcoind().map_err(|e| Error::Server(e.into()))?; | ||
|
||
// in a payment processor where the sender could go offline, this is where you schedule to broadcast the original_tx | ||
let _to_broadcast_in_failure_case = proposal.extract_tx_to_schedule_broadcast(); | ||
|
||
// The network is used for checks later | ||
let network = | ||
bitcoind.get_blockchain_info().map_err(|e| Error::Server(e.into())).and_then( | ||
|info| bitcoin::Network::from_str(&info.chain).map_err(|e| Error::Server(e.into())), | ||
)?; | ||
|
||
// Receive Check 1: Can Broadcast | ||
let proposal = proposal.check_broadcast_suitability(None, |tx| { | ||
let raw_tx = bitcoin::consensus::encode::serialize_hex(&tx); | ||
let mempool_results = | ||
bitcoind.test_mempool_accept(&[raw_tx]).map_err(|e| Error::Server(e.into()))?; | ||
match mempool_results.first() { | ||
Some(result) => Ok(result.allowed), | ||
None => Err(Error::Server( | ||
anyhow!("No mempool results returned on broadcast check").into(), | ||
)), | ||
} | ||
})?; | ||
tracing::trace!("check1"); | ||
|
||
// Receive Check 2: receiver can't sign for proposal inputs | ||
let proposal = proposal.check_inputs_not_owned(|input| { | ||
if let Ok(address) = bitcoin::BdkAddress::from_script(input, network) { | ||
bitcoind | ||
.get_address_info(&address) | ||
.map(|info| info.is_mine.unwrap_or(false)) | ||
.map_err(|e| Error::Server(e.into())) | ||
} else { | ||
Ok(false) | ||
} | ||
})?; | ||
tracing::trace!("check2"); | ||
// Receive Check 3: receiver can't sign for proposal inputs | ||
let proposal = proposal.check_no_mixed_input_scripts()?; | ||
tracing::trace!("check3"); | ||
|
||
// Receive Check 4: have we seen this input before? More of a check for non-interactive i.e. payment processor receivers. | ||
let payjoin = proposal.check_no_inputs_seen_before(|input| { | ||
// TODO implement input_seen_before database check | ||
// Ok(!self.insert_input_seen_before(*input).map_err(|e| Error::Server(e.into()))?) | ||
Ok(false) | ||
})?; | ||
tracing::trace!("check4"); | ||
|
||
let mut provisional_payjoin = payjoin.identify_receiver_outputs(|output_script| { | ||
if let Ok(address) = bitcoin::BdkAddress::from_script(output_script, network) { | ||
bitcoind | ||
.get_address_info(&address) | ||
.map(|info| info.is_mine.unwrap_or(false)) | ||
.map_err(|e| Error::Server(e.into())) | ||
} else { | ||
Ok(false) | ||
} | ||
})?; | ||
|
||
if !self.config.sub_only { | ||
// Select receiver payjoin inputs. | ||
_ = try_contributing_inputs(&mut provisional_payjoin, &bitcoind) | ||
.map_err(|e| tracing::warn!("Failed to contribute inputs: {}", e)); | ||
} | ||
|
||
let receiver_substitute_address = bitcoind | ||
.get_new_address(None, None) | ||
.map_err(|e| Error::Server(e.into()))? | ||
.assume_checked(); | ||
provisional_payjoin.substitute_output_address(receiver_substitute_address); | ||
|
||
let payjoin_proposal = provisional_payjoin.finalize_proposal( | ||
|psbt: &bitcoin::psbt::Psbt| { | ||
bitcoind | ||
.wallet_process_psbt(&base64::encode(psbt.serialize()), None, None, Some(false)) | ||
.map(|res| bitcoin::psbt::Psbt::from_str(&res.psbt).map_err(|e| Error::Server(e.into()))) | ||
.map_err(|e| Error::Server(e.into()))? | ||
}, | ||
None, // TODO set to bitcoin::FeeRate::MIN or similar | ||
)?; | ||
let payjoin_proposal_psbt = payjoin_proposal.psbt(); | ||
println!( | ||
"Responded with Payjoin proposal {}", | ||
payjoin_proposal_psbt.clone().extract_tx().txid() | ||
); | ||
Ok(payjoin_proposal) | ||
} | ||
|
||
|
||
fn try_contributing_inputs( | ||
payjoin: &mut ProvisionalProposal, | ||
bitcoind: &bitcoincore_rpc::Client, | ||
) -> Result<()> { | ||
use bitcoin::OutPoint; | ||
|
||
let available_inputs = bitcoind | ||
.list_unspent(None, None, None, None, None) | ||
.context("Failed to list unspent from bitcoind")?; | ||
let candidate_inputs: HashMap<bitcoin::Amount, OutPoint> = available_inputs | ||
.iter() | ||
.map(|i| (i.amount, OutPoint { txid: i.txid, vout: i.vout })) | ||
.collect(); | ||
|
||
let selected_outpoint = payjoin.try_preserving_privacy(candidate_inputs).expect("gg"); | ||
let selected_utxo = available_inputs | ||
.iter() | ||
.find(|i| i.txid == selected_outpoint.txid && i.vout == selected_outpoint.vout) | ||
.context("This shouldn't happen. Failed to retrieve the privacy preserving utxo from those we provided to the seclector.")?; | ||
tracing::debug!("selected utxo: {:#?}", selected_utxo); | ||
|
||
// calculate receiver payjoin outputs given receiver payjoin inputs and original_psbt, | ||
let txo_to_contribute = bitcoin::TxOut { | ||
value: selected_utxo.amount.to_sat(), | ||
script_pubkey: selected_utxo.script_pub_key.clone(), | ||
}; | ||
let outpoint_to_contribute = | ||
bitcoin::OutPoint { txid: selected_utxo.txid, vout: selected_utxo.vout }; | ||
payjoin.contribute_witness_input(txo_to_contribute, outpoint_to_contribute); | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
use serde::{Deserialize, Serialize}; | ||
|
||
#[derive(Clone, Debug, Deserialize, Serialize)] | ||
pub struct PayjoinConfig { | ||
#[serde(default = "default_port")] | ||
pub listen_port: u16, | ||
} | ||
impl Default for PayjoinConfig { | ||
fn default() -> Self { | ||
Self { | ||
listen_port: default_port(), | ||
} | ||
} | ||
} | ||
|
||
fn default_port() -> u16 { | ||
8088 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
use thiserror::Error; | ||
|
||
#[allow(clippy::large_enum_variant)] | ||
#[derive(Error, Debug)] | ||
pub enum PayjoinError { | ||
#[error("PayjoinError - Error")] | ||
Error, | ||
} |
Oops, something went wrong.