Skip to content

Commit 52464d8

Browse files
committed
f
1 parent 2b21745 commit 52464d8

File tree

7 files changed

+201
-75
lines changed

7 files changed

+201
-75
lines changed

src/builder.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1024,8 +1024,10 @@ fn build_with_store_internal(
10241024
if let Some(pj_config) = payjoin_config {
10251025
payjoin_sender = Some(Arc::new(PayjoinSender::new(
10261026
Arc::clone(&logger),
1027-
Arc::clone(&wallet),
10281027
pj_config.payjoin_relay.clone(),
1028+
Arc::clone(&tx_sync),
1029+
Arc::clone(&event_queue),
1030+
Arc::clone(&wallet),
10291031
)));
10301032
payjoin_receiver = Some(Arc::new(PayjoinReceiver::new(
10311033
Arc::clone(&logger),

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ impl Node {
404404
];
405405
if let Some(sync_payjoin) = sync_payjoin.as_ref() {
406406
confirmables.push(sync_payjoin.as_ref() as &(dyn Confirm + Sync + Send));
407-
}
407+
}
408408
let now = Instant::now();
409409
let timeout_fut = tokio::time::timeout(Duration::from_secs(LDK_WALLET_SYNC_TIMEOUT_SECS), tx_sync.sync(confirmables));
410410
match timeout_fut.await {

src/payment/payjoin/mod.rs

+14-29
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Holds a payment handler allowing to send Payjoin payments.
22
3-
use lightning::chain::{chaininterface::BroadcasterInterface, Filter};
3+
use lightning::chain::chaininterface::BroadcasterInterface;
44

55
use crate::config::{PAYJOIN_REQUEST_TOTAL_DURATION, PAYJOIN_RETRY_INTERVAL};
66
use crate::logger::{log_error, log_info, FilesystemLogger, Logger};
@@ -160,7 +160,6 @@ impl PayjoinPayment {
160160
}
161161
_ = interval.tick() => {
162162
let payjoin_uri = payjoin_uri.clone();
163-
164163
let (request, context) =
165164
payjoin::send::RequestBuilder::from_psbt_and_uri(original_psbt.clone(), payjoin_uri)
166165
.and_then(|b| b.build_non_incentivizing())
@@ -171,24 +170,22 @@ impl PayjoinPayment {
171170
match context.process_response(&mut response.as_slice()) {
172171
Ok(Some(payjoin_proposal_psbt)) => {
173172
let payjoin_proposal_psbt = &mut payjoin_proposal_psbt.clone();
174-
let is_signed = wallet.sign_payjoin_proposal(payjoin_proposal_psbt, &mut original_psbt.clone()).unwrap();
175-
if is_signed {
176-
let tx = payjoin_proposal_psbt.clone().extract_tx();
177-
let inputs = tx.output.clone();
178-
let input = inputs.iter().find(|input| {
179-
wallet.is_mine(&input.script_pubkey).unwrap_or(false)
180-
}).unwrap().script_pubkey.clone();
181-
payjoin_sender.register_tx(&tx.txid(), &input);
182-
tx_broadcaster.broadcast_transactions(&[&tx]);
183-
let txid = tx.txid();
184-
let _ = event_queue.add_event(Event::PayjoinPaymentPending { txid });
185-
} else {
186-
let _ = event_queue
187-
.add_event(Event::PayjoinTxSendFailed { reason: "Unable to sign proposal".to_string(), });
188-
break;
173+
match payjoin_sender.finalise_payjoin_transaction(payjoin_proposal_psbt, &mut original_psbt.clone()) {
174+
Ok(tx) => {
175+
tx_broadcaster.broadcast_transactions(&[&tx]);
176+
let txid = tx.txid();
177+
let _ = event_queue.add_event(Event::PayjoinPaymentPending { txid });
178+
}
179+
Err(e) => {
180+
dbg!(&e);
181+
let _ = event_queue
182+
.add_event(Event::PayjoinTxSendFailed { reason: "Unable to sign proposal".to_string(), });
183+
break;
184+
}
189185
}
190186
},
191187
Err(e) => {
188+
dbg!(&e);
192189
let _ = event_queue
193190
.add_event(Event::PayjoinTxSendFailed { reason: e.to_string() });
194191
log_error!(logger, "Error processing Payjoin response: {}", e);
@@ -344,15 +341,3 @@ impl PayjoinPayment {
344341
}
345342
}
346343
}
347-
348-
// use crate::lightning::chain::{Filter, WatchedOutput};
349-
350-
// impl Filter for PayjoinPayment {
351-
// fn register_tx(&self, txid: &bitcoin::Txid, script_pubkey: &bitcoin::Script) {
352-
// if let Some(receiver) = &self.receiver {
353-
// receiver.register_tx(txid);
354-
// }
355-
// }
356-
// fn register_output(&self, output: WatchOutput) {
357-
// }
358-
// }

src/payment/payjoin/send.rs

+155-42
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,70 @@
11
#![allow(unused_variables)]
22
use crate::config::{PAYJOIN_REQUEST_TIMEOUT, PAYJOIN_RETRY_INTERVAL};
3+
use crate::error::Error;
34
use crate::io::utils::ohttp_headers;
45
use crate::logger::FilesystemLogger;
5-
use crate::types::Wallet;
6+
use crate::types::{ChainSource, EventQueue, Wallet};
7+
use crate::Event;
68

79
use bitcoin::block::Header;
8-
use bitcoin::{BlockHash, Script, ScriptBuf, Txid};
10+
use bitcoin::psbt::Psbt;
11+
use bitcoin::{BlockHash, Script, Transaction, Txid};
12+
use lightning::chain::channelmonitor::ANTI_REORG_DELAY;
913
use lightning::chain::transaction::TransactionData;
10-
use lightning::chain::WatchedOutput;
14+
use lightning::chain::{BestBlock, Filter, WatchedOutput};
1115
use lightning::util::logger::Logger;
1216
use lightning::{log_error, log_info};
1317

14-
use std::sync::Arc;
18+
use std::sync::{Arc, Mutex};
1519

20+
#[derive(Clone, Debug)]
1621
enum PayjoinTransaction {
17-
PendingInitialBroadcast,
18-
PendingFirstConfirmation,
19-
PendingThresholdConfirmation,
22+
PendingFirstConfirmation {
23+
tx: Transaction,
24+
first_broadcast_height: u32,
25+
first_broadcast_hash: BlockHash,
26+
},
27+
PendingThresholdConfirmations {
28+
tx: Transaction,
29+
first_broadcast_height: u32,
30+
first_broadcast_hash: BlockHash,
31+
latest_confirmation_height: u32,
32+
latest_confirmation_hash: BlockHash,
33+
},
34+
}
35+
36+
impl PayjoinTransaction {
37+
fn txid(&self) -> Option<Txid> {
38+
match self {
39+
PayjoinTransaction::PendingFirstConfirmation { tx, .. } => Some(tx.txid()),
40+
PayjoinTransaction::PendingThresholdConfirmations { tx, .. } => Some(tx.txid()),
41+
}
42+
}
2043
}
2144

2245
pub(crate) struct PayjoinSender {
2346
logger: Arc<FilesystemLogger>,
24-
wallet: Arc<Wallet>,
2547
payjoin_relay: payjoin::Url,
26-
transaction_queue: std::sync::Mutex<Vec<(Txid, ScriptBuf)>>,
48+
chain_source: Arc<ChainSource>,
49+
best_known_block: Mutex<Option<BestBlock>>,
50+
transactions: Mutex<Vec<PayjoinTransaction>>,
51+
event_queue: Arc<EventQueue>,
52+
wallet: Arc<Wallet>,
2753
}
2854

2955
impl PayjoinSender {
3056
pub(crate) fn new(
31-
logger: Arc<FilesystemLogger>, wallet: Arc<Wallet>, payjoin_relay: payjoin::Url,
57+
logger: Arc<FilesystemLogger>, payjoin_relay: payjoin::Url, chain_source: Arc<ChainSource>,
58+
event_queue: Arc<EventQueue>, wallet: Arc<Wallet>,
3259
) -> Self {
3360
Self {
3461
logger,
35-
wallet,
3662
payjoin_relay,
37-
transaction_queue: std::sync::Mutex::new(Vec::new()),
63+
transactions: Mutex::new(Vec::new()),
64+
best_known_block: Mutex::new(None),
65+
chain_source,
66+
event_queue,
67+
wallet,
3868
}
3969
}
4070

@@ -94,48 +124,131 @@ impl PayjoinSender {
94124
return None;
95125
}
96126
}
127+
128+
fn internal_transactions_confirmed(
129+
&self, header: &Header, txdata: &TransactionData, height: u32,
130+
) {
131+
let (index, tx) = txdata[0];
132+
let txid = tx.txid();
133+
let mut transactions = self.transactions.lock().unwrap();
134+
let position = transactions.iter().position(|o| o.txid() == Some(txid)).unwrap();
135+
let pj_tx = transactions.remove(position);
136+
dbg!("found confirmed", &pj_tx);
137+
let pj_tx = match pj_tx {
138+
PayjoinTransaction::PendingFirstConfirmation {
139+
ref tx,
140+
first_broadcast_height,
141+
first_broadcast_hash,
142+
} => transactions.push(PayjoinTransaction::PendingThresholdConfirmations {
143+
tx: tx.clone(),
144+
first_broadcast_height,
145+
first_broadcast_hash,
146+
latest_confirmation_height: height,
147+
latest_confirmation_hash: header.block_hash(),
148+
}),
149+
PayjoinTransaction::PendingThresholdConfirmations {
150+
ref tx,
151+
first_broadcast_height,
152+
first_broadcast_hash,
153+
latest_confirmation_height,
154+
latest_confirmation_hash,
155+
} => {
156+
dbg!("Transaction confirmed here");
157+
dbg!("height: {}", height);
158+
dbg!("first_broadcast_height: {}", first_broadcast_height);
159+
if height - first_broadcast_height >= ANTI_REORG_DELAY {
160+
let _ = self.event_queue.add_event(Event::PayjoinTxSendSuccess { txid });
161+
} else {
162+
transactions.push(PayjoinTransaction::PendingThresholdConfirmations {
163+
tx: tx.clone(),
164+
first_broadcast_height,
165+
first_broadcast_hash,
166+
latest_confirmation_height: height,
167+
latest_confirmation_hash: header.block_hash(),
168+
});
169+
}
170+
},
171+
};
172+
// dbg!("here blocked");
173+
// *self.transactions.lock().unwrap() = transactions.clone();
174+
// dbg!("here2 unblocked");
175+
}
176+
177+
pub(crate) fn finalise_payjoin_transaction(
178+
&self, payjoin_proposal: &mut Psbt, original_psbt: &mut Psbt,
179+
) -> Result<Transaction, Error> {
180+
let wallet = self.wallet.clone();
181+
wallet.sign_payjoin_proposal(payjoin_proposal, original_psbt)?;
182+
let tx = payjoin_proposal.clone().extract_tx();
183+
let our_input = tx.output.iter().find(|output| {
184+
wallet.is_mine(&output.script_pubkey).unwrap_or(false)
185+
});
186+
if let Some(our_input) = our_input {
187+
let best_known_block = self.best_known_block.lock().unwrap();
188+
dbg!(&best_known_block);
189+
let best_known_block = best_known_block.as_ref();
190+
dbg!(&best_known_block);
191+
let (current_height, current_hash) = match best_known_block {
192+
Some(b) => (b.height, b.block_hash),
193+
None => return Err(Error::PayjoinReceiverRequestValidationFailed), // fixeror
194+
};
195+
self.transactions.lock().unwrap().push(
196+
PayjoinTransaction::PendingFirstConfirmation {
197+
tx: tx.clone(),
198+
first_broadcast_height: current_height,
199+
first_broadcast_hash: current_hash,
200+
}
201+
);
202+
self.register_tx(&tx.txid(), &our_input.script_pubkey);
203+
Ok(tx)
204+
} else {
205+
Err(Error::PayjoinReceiverRequestValidationFailed) // fixeror
206+
}
207+
}
97208
}
98209

99-
impl lightning::chain::Filter for PayjoinSender {
210+
impl Filter for PayjoinSender {
100211
fn register_tx(&self, txid: &Txid, script_pubkey: &Script) {
101212
dbg!("Registering transaction {:?}", txid);
102-
self.transaction_queue.lock().unwrap().push((txid.clone(), script_pubkey.into()));
213+
self.chain_source.register_tx(txid, script_pubkey);
214+
}
215+
216+
fn register_output(&self, output: WatchedOutput) {
217+
self.chain_source.register_output(output);
103218
}
104-
fn register_output(&self, output: WatchedOutput) {}
105219
}
106220

107221
impl lightning::chain::Confirm for PayjoinSender {
108222
fn transactions_confirmed(&self, header: &Header, txdata: &TransactionData, height: u32) {
109-
dbg!("Confirmed transaction {:?}", txdata);
110-
// let (index, tx) = txdata[0];
111-
// let txid = tx.txid();
112-
// let my_input =
113-
// tx.input.iter().find(|input| self.wallet.is_mine(&input.script_sig).unwrap_or(false));
114-
// if let Some(my_input) = my_input {
115-
// self.transaction_queue.push((txid, my_input.script_sig))
116-
// }
223+
dbg!("Confirmed transaction");
224+
self.internal_transactions_confirmed(header, txdata, height);
117225
}
118226
fn transaction_unconfirmed(&self, txid: &Txid) {
119-
dbg!("Unconfirmed transaction {:?}", txid);
120-
}
227+
dbg!("Unconfirmed transaction {:?}", txid);
228+
}
121229
fn best_block_updated(&self, header: &Header, height: u32) {
122-
dbg!("Best block updated {:?}", header);
123-
}
230+
dbg!("Best block updated {:?}", height);
231+
*self.best_known_block.lock().unwrap() =
232+
Some(BestBlock { height, block_hash: header.block_hash() });
233+
}
124234
fn get_relevant_txids(&self) -> Vec<(Txid, u32, Option<BlockHash>)> {
125-
// let state_lock = self.sweeper_state.lock().unwrap();
126-
// state_lock
127-
// .outputs
128-
// .iter()
129-
// .filter_map(|o| match o.status {
130-
// OutputSpendStatus::PendingThresholdConfirmations {
131-
// ref latest_spending_tx,
132-
// confirmation_height,
133-
// confirmation_hash,
134-
// ..
135-
// } => Some((latest_spending_tx.txid(), confirmation_height, Some(confirmation_hash))),
136-
// _ => None,
137-
// })
138-
// .collect::<Vec<_>>()
139-
vec![]
235+
dbg!("Getting relevant txids");
236+
let state_lock = self.transactions.lock().unwrap();
237+
state_lock
238+
.iter()
239+
.filter_map(|o| match o {
240+
PayjoinTransaction::PendingThresholdConfirmations {
241+
tx,
242+
latest_confirmation_height,
243+
latest_confirmation_hash,
244+
..
245+
} => Some((
246+
tx.clone().txid(),
247+
latest_confirmation_height.clone(),
248+
Some(latest_confirmation_hash.clone()),
249+
)),
250+
_ => None,
251+
})
252+
.collect::<Vec<_>>()
140253
}
141254
}

src/wallet.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ where
193193

194194
pub(crate) fn sign_payjoin_proposal(
195195
&self, payjoin_proposal_psbt: &mut Psbt, original_psbt: &mut Psbt,
196-
) -> Result<bool, Error> {
196+
) -> Result<(), Error> {
197197
// BDK only signs scripts that match its target descriptor by iterating through input map.
198198
// The BIP 78 spec makes receiver clear sender input map UTXOs, so process_response will
199199
// fail unless they're cleared. A PSBT unsigned_tx.input references input OutPoints and
@@ -217,7 +217,12 @@ where
217217
}
218218
let wallet = self.inner.lock().unwrap();
219219
let is_signed = wallet.sign(payjoin_proposal_psbt, SignOptions::default())?;
220-
Ok(is_signed)
220+
if !is_signed {
221+
dbg!("Failed to sign payjoin proposal");
222+
log_error!(self.logger, "Failed to sign payjoin proposal");
223+
return Err(Error::WalletOperationFailed);
224+
}
225+
Ok(())
221226
}
222227

223228
// Returns a list of unspent outputs that can be used as inputs to improve the privacy of a

tests/common/mod.rs

+17
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,23 @@ macro_rules! expect_payjoin_payment_pending_event {
164164

165165
pub(crate) use expect_payjoin_payment_pending_event;
166166

167+
macro_rules! expect_payjoin_payment_success_event {
168+
($node: expr) => {{
169+
match $node.wait_next_event() {
170+
ref e @ Event::PayjoinTxSendSuccess { txid } => {
171+
println!("{} got event {:?}", $node.node_id(), e);
172+
$node.event_handled();
173+
txid
174+
},
175+
ref e => {
176+
panic!("{} got unexpected event!: {:?}", std::stringify!($node), e);
177+
},
178+
}
179+
}};
180+
}
181+
182+
pub(crate) use expect_payjoin_payment_success_event;
183+
167184
pub(crate) fn setup_bitcoind_and_electrsd() -> (BitcoinD, ElectrsD) {
168185
let bitcoind_exe =
169186
env::var("BITCOIND_EXE").ok().or_else(|| bitcoind::downloaded_exe_path().ok()).expect(

0 commit comments

Comments
 (0)