Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b1ac224

Browse files
committedJun 11, 2024··
Open LN channel from incoming Payjoin tx
This commit allows users to schedule a channel that will opened once a Payjoin request received. This can save users 1 extra onchain transaction fees. The Payjoin flow is normal with the following caveats: 1. We use `Payjoin::ProvisionalProposal::substitue_output_address` to point to the multisig output script as retrived from `LdkEvent::FundingGeneratingReady`. 2. We dont try to preserve privacy in Payjoin channel opening transactions. 3. We wait with our response to the Payjoin sender until a `Ldk::Event::FundingTxBroadcastSafe` event is received.
1 parent f33294d commit b1ac224

9 files changed

+640
-31
lines changed
 

‎Cargo.toml

+19-19
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,23 @@ panic = 'abort' # Abort on panic
2828
default = []
2929

3030
[dependencies]
31-
lightning = { version = "0.0.123", features = ["std"] }
32-
lightning-invoice = { version = "0.31.0" }
33-
lightning-net-tokio = { version = "0.0.123" }
34-
lightning-persister = { version = "0.0.123" }
35-
lightning-background-processor = { version = "0.0.123", features = ["futures"] }
36-
lightning-rapid-gossip-sync = { version = "0.0.123" }
37-
lightning-transaction-sync = { version = "0.0.123", features = ["esplora-async-https", "time"] }
38-
lightning-liquidity = { version = "0.1.0-alpha.4", features = ["std"] }
39-
40-
#lightning = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main", features = ["std"] }
41-
#lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main" }
42-
#lightning-net-tokio = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main" }
43-
#lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main" }
44-
#lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main", features = ["futures"] }
45-
#lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main" }
46-
#lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main", features = ["esplora-async"] }
47-
#lightning-liquidity = { git = "https://github.com/lightningdevkit/lightning-liquidity", branch="main", features = ["std"] }
31+
# lightning = { version = "0.0.123", features = ["std"] }
32+
# lightning-invoice = { version = "0.31.0" }
33+
# lightning-net-tokio = { version = "0.0.123" }
34+
# lightning-persister = { version = "0.0.123" }
35+
# lightning-background-processor = { version = "0.0.123", features = ["futures"] }
36+
# lightning-rapid-gossip-sync = { version = "0.0.123" }
37+
# lightning-transaction-sync = { version = "0.0.123", features = ["esplora-async-https", "time"] }
38+
# lightning-liquidity = { version = "0.1.0-alpha.4", features = ["std"] }
39+
40+
lightning = { git = "https://github.com/jbesraa/rust-lightning.git", branch="0.0.123-with-funding-brodsafe-event", features = ["std"] }
41+
lightning-invoice = { git = "https://github.com/jbesraa/rust-lightning.git", branch="0.0.123-with-funding-brodsafe-event" }
42+
lightning-net-tokio = { git = "https://github.com/jbesraa/rust-lightning.git", branch="0.0.123-with-funding-brodsafe-event" }
43+
lightning-persister = { git = "https://github.com/jbesraa/rust-lightning.git", branch="0.0.123-with-funding-brodsafe-event" }
44+
lightning-background-processor = { git = "https://github.com/jbesraa/rust-lightning.git", branch="0.0.123-with-funding-brodsafe-event", features = ["futures"] }
45+
lightning-rapid-gossip-sync = { git = "https://github.com/jbesraa/rust-lightning.git", branch="0.0.123-with-funding-brodsafe-event" }
46+
lightning-transaction-sync = { git = "https://github.com/jbesraa/rust-lightning.git", branch="0.0.123-with-funding-brodsafe-event", features = ["esplora-async"] }
47+
lightning-liquidity = { git = "https://github.com/jbesraa/lightning-liquidity", branch="pj-fixes", features = ["std"] }
4848

4949
#lightning = { path = "../rust-lightning/lightning", features = ["std"] }
5050
#lightning-invoice = { path = "../rust-lightning/lightning-invoice" }
@@ -78,8 +78,8 @@ prost = { version = "0.11.6", default-features = false}
7878
winapi = { version = "0.3", features = ["winbase"] }
7979

8080
[dev-dependencies]
81-
lightning = { version = "0.0.123", features = ["std", "_test_utils"] }
82-
#lightning = { git = "https://github.com/lightningdevkit/rust-lightning", branch="main", features = ["std", "_test_utils"] }
81+
# lightning = { version = "0.0.123", features = ["std", "_test_utils"] }
82+
lightning = { git = "https://github.com/jbesraa/rust-lightning.git", branch="0.0.123-with-funding-brodsafe-event", features = ["std", "_test_utils"] }
8383
electrum-client = { version = "0.15.1", default-features = true }
8484
bitcoincore-rpc = { version = "0.17.0", default-features = false }
8585
proptest = "1.0.0"

‎src/builder.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1070,6 +1070,7 @@ fn build_with_store_internal(
10701070
Some(Arc::new(PayjoinReceiver::new(
10711071
Arc::clone(&logger),
10721072
Arc::clone(&wallet),
1073+
Arc::clone(&channel_manager),
10731074
directory,
10741075
relay,
10751076
ohttp_keys,

‎src/event.rs

+57-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::payjoin_receiver::PayjoinReceiver;
12
use crate::types::{DynStore, Sweeper, Wallet};
23

34
use crate::{
@@ -323,6 +324,7 @@ where
323324
network_graph: Arc<NetworkGraph>,
324325
payment_store: Arc<PaymentStore<L>>,
325326
peer_store: Arc<PeerStore<L>>,
327+
payjoin_receiver: Option<Arc<PayjoinReceiver>>,
326328
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
327329
logger: L,
328330
config: Arc<Config>,
@@ -336,8 +338,8 @@ where
336338
event_queue: Arc<EventQueue<L>>, wallet: Arc<Wallet>, channel_manager: Arc<ChannelManager>,
337339
connection_manager: Arc<ConnectionManager<L>>, output_sweeper: Arc<Sweeper>,
338340
network_graph: Arc<NetworkGraph>, payment_store: Arc<PaymentStore<L>>,
339-
peer_store: Arc<PeerStore<L>>, runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
340-
logger: L, config: Arc<Config>,
341+
payjoin_receiver: Option<Arc<PayjoinReceiver>>, peer_store: Arc<PeerStore<L>>,
342+
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>, logger: L, config: Arc<Config>,
341343
) -> Self {
342344
Self {
343345
event_queue,
@@ -347,6 +349,7 @@ where
347349
output_sweeper,
348350
network_graph,
349351
payment_store,
352+
payjoin_receiver,
350353
peer_store,
351354
logger,
352355
runtime,
@@ -361,6 +364,7 @@ where
361364
counterparty_node_id,
362365
channel_value_satoshis,
363366
output_script,
367+
user_channel_id,
364368
..
365369
} => {
366370
// Construct the raw transaction with the output that is paid the amount of the
@@ -371,6 +375,18 @@ where
371375
let cur_height = self.channel_manager.current_best_block().height;
372376
let locktime = LockTime::from_height(cur_height).unwrap_or(LockTime::ZERO);
373377

378+
if let Some(payjoin_receiver) = self.payjoin_receiver.clone() {
379+
if payjoin_receiver
380+
.set_channel_accepted(
381+
user_channel_id,
382+
&output_script,
383+
temporary_channel_id.0,
384+
)
385+
.await
386+
{
387+
return;
388+
}
389+
}
374390
// Sign the final funding transaction and broadcast it.
375391
match self.wallet.create_funding_transaction(
376392
output_script,
@@ -918,6 +934,45 @@ where
918934
);
919935
}
920936
},
937+
LdkEvent::FundingTxBroadcastSafe { funding_tx, .. } => {
938+
use crate::io::utils::ohttp_headers;
939+
if let Some(payjoin_receiver) = self.payjoin_receiver.clone() {
940+
let is_payjoin_channel =
941+
payjoin_receiver.set_funding_tx_signed(funding_tx.clone()).await;
942+
if let Some((url, body)) = is_payjoin_channel {
943+
log_info!(
944+
self.logger,
945+
"Detected payjoin channel transaction. Sending payjoin sender request for transaction {}",
946+
funding_tx.txid()
947+
);
948+
let headers = ohttp_headers();
949+
let client = reqwest::Client::builder().build().unwrap();
950+
match client.post(url).body(body).headers(headers).send().await {
951+
Ok(response) => {
952+
if response.status().is_success() {
953+
log_info!(
954+
self.logger,
955+
"Responded to 'Payjoin Sender' successfuly"
956+
);
957+
} else {
958+
log_info!(
959+
self.logger,
960+
"Got unsuccessful response from 'Payjoin Sender': {}",
961+
response.status()
962+
);
963+
}
964+
},
965+
Err(e) => {
966+
log_error!(
967+
self.logger,
968+
"Failed to send a response to 'Payjoin Sender': {}",
969+
e
970+
);
971+
},
972+
};
973+
}
974+
}
975+
},
921976
LdkEvent::ChannelPending {
922977
channel_id,
923978
user_channel_id,

‎src/lib.rs

+8
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ pub mod io;
8888
mod liquidity;
8989
mod logger;
9090
mod message_handler;
91+
mod payjoin_channel_scheduler;
9192
mod payjoin_receiver;
9293
mod payjoin_sender;
9394
pub mod payment;
@@ -661,6 +662,7 @@ impl Node {
661662
Arc::clone(&self.output_sweeper),
662663
Arc::clone(&self.network_graph),
663664
Arc::clone(&self.payment_store),
665+
self.payjoin_receiver.clone(),
664666
Arc::clone(&self.peer_store),
665667
Arc::clone(&self.runtime),
666668
Arc::clone(&self.logger),
@@ -977,6 +979,9 @@ impl Node {
977979
payjoin_sender.map(Arc::clone),
978980
payjoin_receiver.map(Arc::clone),
979981
Arc::clone(&self.config),
982+
Arc::clone(&self.peer_store),
983+
Arc::clone(&self.channel_manager),
984+
Arc::clone(&self.connection_manager),
980985
)
981986
}
982987

@@ -1003,6 +1008,9 @@ impl Node {
10031008
payjoin_sender.map(Arc::clone),
10041009
payjoin_receiver.map(Arc::clone),
10051010
Arc::clone(&self.config),
1011+
Arc::clone(&self.peer_store),
1012+
Arc::clone(&self.channel_manager),
1013+
Arc::clone(&self.connection_manager),
10061014
)
10071015
}
10081016

‎src/payjoin_channel_scheduler.rs

+254
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
use bitcoin::{secp256k1::PublicKey, ScriptBuf, TxOut};
2+
3+
#[derive(Clone)]
4+
pub struct PayjoinChannelScheduler {
5+
channels: Vec<PayjoinChannel>,
6+
}
7+
8+
impl PayjoinChannelScheduler {
9+
pub(crate) fn new() -> Self {
10+
Self { channels: vec![] }
11+
}
12+
13+
pub(crate) fn schedule(
14+
&mut self, channel_value_satoshi: bitcoin::Amount, counterparty_node_id: PublicKey,
15+
channel_id: u128,
16+
) {
17+
let channel = PayjoinChannel::new(channel_value_satoshi, counterparty_node_id, channel_id);
18+
match channel.state {
19+
ScheduledChannelState::ChannelCreated => {
20+
self.channels.push(channel);
21+
},
22+
_ => {},
23+
}
24+
}
25+
26+
pub(crate) fn set_channel_accepted(
27+
&mut self, channel_id: u128, output_script: &ScriptBuf, temporary_channel_id: [u8; 32],
28+
) -> bool {
29+
for channel in &mut self.channels {
30+
if channel.channel_id() == channel_id {
31+
channel.state.set_channel_accepted(output_script, temporary_channel_id);
32+
return true;
33+
}
34+
}
35+
false
36+
}
37+
38+
pub(crate) fn set_funding_tx_created(
39+
&mut self, channel_id: u128, url: &payjoin::Url, body: Vec<u8>,
40+
) -> bool {
41+
for channel in &mut self.channels {
42+
if channel.channel_id() == channel_id {
43+
return channel.state.set_channel_funding_tx_created(url.clone(), body);
44+
}
45+
}
46+
false
47+
}
48+
49+
pub(crate) fn set_funding_tx_signed(
50+
&mut self, tx: bitcoin::Transaction,
51+
) -> Option<(payjoin::Url, Vec<u8>)> {
52+
for output in tx.output.iter() {
53+
if let Some(mut channel) = self.internal_find_by_tx_out(&output.clone()) {
54+
let info = channel.request_info();
55+
if info.is_some() && channel.state.set_channel_funding_tx_signed(output.clone()) {
56+
return info;
57+
}
58+
}
59+
}
60+
None
61+
}
62+
63+
/// Get the next channel matching the given channel amount.
64+
///
65+
/// The channel must be in accepted state.
66+
///
67+
/// If more than one channel matches the given channel amount, the channel with the oldest
68+
/// creation date will be returned.
69+
pub(crate) fn get_next_channel(
70+
&self, channel_amount: bitcoin::Amount,
71+
) -> Option<(u128, bitcoin::Address, [u8; 32], bitcoin::Amount, bitcoin::secp256k1::PublicKey)>
72+
{
73+
let channel = self
74+
.channels
75+
.iter()
76+
.filter(|channel| {
77+
channel.channel_value_satoshi() == channel_amount
78+
&& channel.is_channel_accepted()
79+
&& channel.output_script().is_some()
80+
&& channel.temporary_channel_id().is_some()
81+
})
82+
.min_by_key(|channel| channel.created_at());
83+
84+
if let Some(channel) = channel {
85+
let address = bitcoin::Address::from_script(
86+
&channel.output_script().unwrap(),
87+
bitcoin::Network::Regtest, // fixme
88+
);
89+
if let Ok(address) = address {
90+
return Some((
91+
channel.channel_id(),
92+
address,
93+
channel.temporary_channel_id().unwrap(),
94+
channel.channel_value_satoshi(),
95+
channel.counterparty_node_id(),
96+
));
97+
}
98+
};
99+
None
100+
}
101+
102+
fn internal_find_by_tx_out(&self, txout: &TxOut) -> Option<PayjoinChannel> {
103+
let channel = self.channels.iter().find(|channel| {
104+
return Some(&txout.script_pubkey) == channel.output_script();
105+
});
106+
channel.cloned()
107+
}
108+
}
109+
110+
#[derive(Clone, Debug)]
111+
pub(crate) struct PayjoinChannel {
112+
state: ScheduledChannelState,
113+
channel_value_satoshi: bitcoin::Amount,
114+
channel_id: u128,
115+
counterparty_node_id: PublicKey,
116+
created_at: u64,
117+
}
118+
119+
impl PayjoinChannel {
120+
pub(crate) fn new(
121+
channel_value_satoshi: bitcoin::Amount, counterparty_node_id: PublicKey, channel_id: u128,
122+
) -> Self {
123+
Self {
124+
state: ScheduledChannelState::ChannelCreated,
125+
channel_value_satoshi,
126+
channel_id,
127+
counterparty_node_id,
128+
created_at: 0,
129+
}
130+
}
131+
132+
fn is_channel_accepted(&self) -> bool {
133+
match self.state {
134+
ScheduledChannelState::ChannelAccepted(..) => true,
135+
_ => false,
136+
}
137+
}
138+
139+
pub(crate) fn channel_value_satoshi(&self) -> bitcoin::Amount {
140+
self.channel_value_satoshi
141+
}
142+
143+
pub(crate) fn channel_id(&self) -> u128 {
144+
self.channel_id
145+
}
146+
147+
pub(crate) fn counterparty_node_id(&self) -> PublicKey {
148+
self.counterparty_node_id
149+
}
150+
151+
pub(crate) fn output_script(&self) -> Option<&ScriptBuf> {
152+
self.state.output_script()
153+
}
154+
155+
pub(crate) fn temporary_channel_id(&self) -> Option<[u8; 32]> {
156+
self.state.temporary_channel_id()
157+
}
158+
159+
pub(crate) fn request_info(&self) -> Option<(payjoin::Url, Vec<u8>)> {
160+
match &self.state {
161+
ScheduledChannelState::FundingTxCreated(_, url, body) => {
162+
Some((url.clone(), body.clone()))
163+
},
164+
_ => None,
165+
}
166+
}
167+
168+
fn created_at(&self) -> u64 {
169+
self.created_at
170+
}
171+
}
172+
173+
#[derive(Clone, Debug)]
174+
struct FundingTxParams {
175+
output_script: ScriptBuf,
176+
temporary_channel_id: [u8; 32],
177+
}
178+
179+
impl FundingTxParams {
180+
fn new(output_script: ScriptBuf, temporary_channel_id: [u8; 32]) -> Self {
181+
Self { output_script, temporary_channel_id }
182+
}
183+
}
184+
185+
#[derive(Clone, Debug)]
186+
enum ScheduledChannelState {
187+
ChannelCreated,
188+
ChannelAccepted(FundingTxParams),
189+
FundingTxCreated(FundingTxParams, payjoin::Url, Vec<u8>),
190+
FundingTxSigned(FundingTxParams, ()),
191+
}
192+
193+
impl ScheduledChannelState {
194+
fn output_script(&self) -> Option<&ScriptBuf> {
195+
match self {
196+
ScheduledChannelState::ChannelAccepted(funding_tx_params) => {
197+
Some(&funding_tx_params.output_script)
198+
},
199+
ScheduledChannelState::FundingTxCreated(funding_tx_params, _, _) => {
200+
Some(&funding_tx_params.output_script)
201+
},
202+
ScheduledChannelState::FundingTxSigned(funding_tx_params, _) => {
203+
Some(&funding_tx_params.output_script)
204+
},
205+
_ => None,
206+
}
207+
}
208+
209+
fn temporary_channel_id(&self) -> Option<[u8; 32]> {
210+
match self {
211+
ScheduledChannelState::ChannelAccepted(funding_tx_params) => {
212+
Some(funding_tx_params.temporary_channel_id)
213+
},
214+
ScheduledChannelState::FundingTxCreated(funding_tx_params, _, _) => {
215+
Some(funding_tx_params.temporary_channel_id)
216+
},
217+
ScheduledChannelState::FundingTxSigned(funding_tx_params, _) => {
218+
Some(funding_tx_params.temporary_channel_id)
219+
},
220+
_ => None,
221+
}
222+
}
223+
224+
fn set_channel_accepted(
225+
&mut self, output_script: &ScriptBuf, temporary_channel_id: [u8; 32],
226+
) -> bool {
227+
if let ScheduledChannelState::ChannelCreated = self {
228+
*self = ScheduledChannelState::ChannelAccepted(FundingTxParams::new(
229+
output_script.clone(),
230+
temporary_channel_id,
231+
));
232+
return true;
233+
}
234+
return false;
235+
}
236+
237+
fn set_channel_funding_tx_created(&mut self, url: payjoin::Url, body: Vec<u8>) -> bool {
238+
if let ScheduledChannelState::ChannelAccepted(funding_tx_params) = self {
239+
*self = ScheduledChannelState::FundingTxCreated(funding_tx_params.clone(), url, body);
240+
return true;
241+
}
242+
return false;
243+
}
244+
245+
fn set_channel_funding_tx_signed(&mut self, output: TxOut) -> bool {
246+
let mut res = false;
247+
if let ScheduledChannelState::FundingTxCreated(funding_tx_params, _, _) = self {
248+
assert_eq!(funding_tx_params.output_script, output.script_pubkey);
249+
*self = ScheduledChannelState::FundingTxSigned(funding_tx_params.clone(), ());
250+
res = true;
251+
}
252+
return res;
253+
}
254+
}

‎src/payjoin_receiver.rs

+107-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use crate::error::Error;
22
use crate::io::utils::ohttp_headers;
33
use crate::logger::FilesystemLogger;
4-
use crate::types::Wallet;
4+
use crate::payjoin_channel_scheduler::{PayjoinChannel, PayjoinChannelScheduler};
5+
use crate::types::{ChannelManager, Wallet};
6+
use bitcoin::{ScriptBuf, Transaction};
7+
use lightning::ln::ChannelId;
58
use lightning::log_info;
69
use lightning::util::logger::Logger;
710
use payjoin::receive::v2::{Enrolled, Enroller, ProvisionalProposal, UncheckedProposal};
@@ -17,6 +20,8 @@ use tokio::sync::RwLock;
1720
pub(crate) struct PayjoinReceiver {
1821
logger: Arc<FilesystemLogger>,
1922
wallet: Arc<Wallet>,
23+
channel_manager: Arc<ChannelManager>,
24+
channel_scheduler: RwLock<PayjoinChannelScheduler>,
2025
/// Directory receiver wish to enroll with
2126
payjoin_directory: Url,
2227
/// Proxy server receiver wish to make requests through
@@ -32,12 +37,14 @@ pub(crate) struct PayjoinReceiver {
3237

3338
impl PayjoinReceiver {
3439
pub(crate) fn new(
35-
logger: Arc<FilesystemLogger>, wallet: Arc<Wallet>, payjoin_relay: Url,
36-
payjoin_directory: Url, ohttp_keys: Option<OhttpKeys>,
40+
logger: Arc<FilesystemLogger>, wallet: Arc<Wallet>, channel_manager: Arc<ChannelManager>,
41+
payjoin_relay: Url, payjoin_directory: Url, ohttp_keys: Option<OhttpKeys>,
3742
) -> Self {
3843
Self {
3944
logger,
4045
wallet,
46+
channel_manager,
47+
channel_scheduler: RwLock::new(PayjoinChannelScheduler::new()),
4148
payjoin_directory,
4249
payjoin_relay,
4350
enrolled: RwLock::new(None),
@@ -85,13 +92,21 @@ impl PayjoinReceiver {
8592
Ok(payjoin_uri)
8693
}
8794

95+
pub(crate) async fn set_channel_accepted(
96+
&self, channel_id: u128, output_script: &ScriptBuf, temporary_channel_id: [u8; 32],
97+
) -> bool {
98+
let mut scheduler = self.channel_scheduler.write().await;
99+
scheduler.set_channel_accepted(channel_id, output_script, temporary_channel_id)
100+
}
101+
88102
/// After enrolling, we should periodacly check if we have received any Payjoin transactions.
89103
///
90104
/// This function will try to fetch pending Payjoin requests from the subdirectory, and if a
91-
/// successful response received, we validate the request as specified in [`BIP78`]. After
92-
/// validation we try to preserve privacy by adding more inputs/outputs to the transaction.
93-
/// Last, we finalise the transaction and send a response back the the Payjoin sender.
94-
///
105+
/// successful response received, we validate the request as specified in [BIP78]. After
106+
/// validation we check if we have a pending matching channel, and if so, we try fund the channel
107+
/// with the incoming funds from the payjoin request. Otherwise, we accept the Payjoin request
108+
/// normally by trying to preserve privacy, finalise the Payjoin proposal and send it back the
109+
/// the Payjoin sender.
95110
///
96111
/// [BIP78]: https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki#user-content-Receivers_original_PSBT_checklist
97112
pub(crate) async fn process_payjoin_request(&self) {
@@ -163,6 +178,7 @@ impl PayjoinReceiver {
163178
return;
164179
},
165180
};
181+
let original_tx = unchecked_proposal.extract_tx_to_schedule_broadcast();
166182
let provisional_proposal = match self.validate_payjoin_request(unchecked_proposal).await
167183
{
168184
Ok(proposal) => proposal,
@@ -171,7 +187,67 @@ impl PayjoinReceiver {
171187
return;
172188
},
173189
};
174-
self.accept_payjoin_transaction(provisional_proposal).await;
190+
let amount = match self.wallet.funds_directed_to_us(&original_tx) {
191+
Ok(a) => a,
192+
Err(e) => {
193+
// This should not happen in practice as the validation checks would fail if
194+
// the sender didnt include us in the outputs
195+
log_info!(self.logger, "Not able to find any ouput directed to us: {}", e);
196+
return;
197+
},
198+
};
199+
let mut scheduler = self.channel_scheduler.write().await;
200+
if let Some(channel) = scheduler.get_next_channel(amount) {
201+
log_info!(self.logger, "Found a channel match for incoming Payjoin request");
202+
let (channel_id, funding_tx_address, temporary_channel_id, _, counterparty_node_id) =
203+
channel;
204+
let mut channel_provisional_proposal = provisional_proposal.clone();
205+
channel_provisional_proposal.substitute_output_address(funding_tx_address);
206+
let payjoin_proposal = match channel_provisional_proposal
207+
.finalize_proposal(|psbt| Ok(psbt.clone()), None)
208+
{
209+
Ok(proposal) => proposal,
210+
Err(e) => {
211+
dbg!(&e);
212+
return;
213+
},
214+
};
215+
let (receiver_request, _) = match payjoin_proposal.clone().extract_v2_req() {
216+
Ok((req, ctx)) => (req, ctx),
217+
Err(e) => {
218+
dbg!(&e);
219+
return;
220+
},
221+
};
222+
let tx = payjoin_proposal.psbt().clone().extract_tx();
223+
scheduler.set_funding_tx_created(
224+
channel_id,
225+
&receiver_request.url,
226+
receiver_request.body,
227+
);
228+
match self.channel_manager.unsafe_manual_funding_transaction_generated(
229+
&ChannelId::from_bytes(temporary_channel_id),
230+
&counterparty_node_id,
231+
tx.clone(),
232+
) {
233+
Ok(_) => {
234+
// Created Funding Transaction and waiting for `FundingTxBroadcastSafe` event before returning a response
235+
log_info!(self.logger, "Created channel funding transaction from Payjoin request and waiting for `FundingTxBroadcastSafe`");
236+
},
237+
Err(_) => {
238+
log_info!(
239+
self.logger,
240+
"Unable to channel create funding tx from Payjoin request"
241+
);
242+
},
243+
}
244+
} else {
245+
log_info!(
246+
self.logger,
247+
"Couldnt match a channel to Payjoin request, accepting normally"
248+
);
249+
self.accept_payjoin_transaction(provisional_proposal).await;
250+
}
175251
} else {
176252
log_info!(self.logger, "Payjoin Receiver: Unable to get enrolled object");
177253
}
@@ -396,6 +472,29 @@ impl PayjoinReceiver {
396472
&& self.ohttp_keys.read().await.deref().is_some()
397473
}
398474

475+
/// Schedule a channel to opened upon receiving a Payjoin tranasction value with the same
476+
/// channel funding amount.
477+
pub(crate) async fn schedule_channel(
478+
&self, amount: bitcoin::Amount, counterparty_node_id: bitcoin::secp256k1::PublicKey,
479+
channel_id: u128,
480+
) {
481+
let channel = PayjoinChannel::new(amount, counterparty_node_id, channel_id);
482+
self.channel_scheduler.write().await.schedule(
483+
channel.channel_value_satoshi(),
484+
channel.counterparty_node_id(),
485+
channel.channel_id(),
486+
);
487+
}
488+
489+
/// This should only be called upon receiving [`Event::FundingTxBroadcastSafe`]
490+
///
491+
/// [`Event::FundingTxBroadcastSafe`]: lightning::events::Event::FundingTxBroadcastSafe
492+
pub(crate) async fn set_funding_tx_signed(
493+
&self, funding_tx: Transaction,
494+
) -> Option<(payjoin::Url, Vec<u8>)> {
495+
self.channel_scheduler.write().await.set_funding_tx_signed(funding_tx)
496+
}
497+
399498
/// Validate an incoming Payjoin request as specified in [BIP78].
400499
///
401500
/// [BIP78]: https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki#user-content-Receivers_original_PSBT_checklist

‎src/payment/payjoin.rs

+80-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
//! Holds a payment handler allowing to send Payjoin payments.
22
3+
use bitcoin::secp256k1::PublicKey;
4+
use lightning::ln::msgs::SocketAddress;
5+
use lightning::util::config::{ChannelHandshakeConfig, UserConfig};
36
use payjoin::PjUri;
47

8+
use crate::connection::ConnectionManager;
9+
use crate::logger::FilesystemLogger;
510
use crate::payjoin_receiver::PayjoinReceiver;
6-
use crate::types::PayjoinSender;
11+
use crate::peer_store::{PeerInfo, PeerStore};
12+
use crate::types::{ChannelManager, PayjoinSender};
713
use crate::{error::Error, Config};
814

915
use std::sync::{Arc, RwLock};
@@ -18,14 +24,19 @@ pub struct PayjoinPayment {
1824
sender: Option<Arc<PayjoinSender>>,
1925
receiver: Option<Arc<PayjoinReceiver>>,
2026
config: Arc<Config>,
27+
peer_store: Arc<PeerStore<Arc<FilesystemLogger>>>,
28+
channel_manager: Arc<ChannelManager>,
29+
connection_manager: Arc<ConnectionManager<Arc<FilesystemLogger>>>,
2130
}
2231

2332
impl PayjoinPayment {
2433
pub(crate) fn new(
2534
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>, sender: Option<Arc<PayjoinSender>>,
2635
receiver: Option<Arc<PayjoinReceiver>>, config: Arc<Config>,
36+
peer_store: Arc<PeerStore<Arc<FilesystemLogger>>>, channel_manager: Arc<ChannelManager>,
37+
connection_manager: Arc<ConnectionManager<Arc<FilesystemLogger>>>,
2738
) -> Self {
28-
Self { runtime, sender, receiver, config }
39+
Self { runtime, sender, receiver, config, peer_store, channel_manager, connection_manager }
2940
}
3041

3142
/// Send an on chain Payjoin transaction to the address specified in the `payjoin_uri`
@@ -104,4 +115,71 @@ impl PayjoinPayment {
104115
Err(Error::PayjoinReceiverUnavailable)
105116
}
106117
}
118+
119+
/// Receive on chain Payjoin transaction and open a channel in a single transaction.
120+
///
121+
/// This method will enroll with the configured Payjoin directory if not already,
122+
/// and before returning a [BIP21] URI pointing to our enrolled subdirectory to share with
123+
/// Payjoin sender, we start the channel opening process and halt it when we receive
124+
/// `accept_channel` from counterparty node. Once the Payjoin request is received, we move
125+
/// forward with the channel opening process.
126+
///
127+
/// [BIP21]: https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki
128+
pub async fn receive_with_channel_opening(
129+
&self, channel_amount_sats: u64, push_msat: Option<u64>, announce_channel: bool,
130+
node_id: PublicKey, address: SocketAddress,
131+
) -> Result<PjUri, Error> {
132+
use rand::Rng;
133+
let rt_lock = self.runtime.read().unwrap();
134+
if rt_lock.is_none() {
135+
return Err(Error::NotRunning);
136+
}
137+
if let Some(receiver) = &self.receiver {
138+
let user_channel_id: u128 = rand::thread_rng().gen::<u128>();
139+
receiver
140+
.schedule_channel(
141+
bitcoin::Amount::from_sat(channel_amount_sats),
142+
node_id,
143+
user_channel_id,
144+
)
145+
.await;
146+
let user_config = UserConfig {
147+
channel_handshake_limits: Default::default(),
148+
channel_handshake_config: ChannelHandshakeConfig {
149+
announced_channel: announce_channel,
150+
..Default::default()
151+
},
152+
..Default::default()
153+
};
154+
let push_msat = push_msat.unwrap_or(0);
155+
let peer_info = PeerInfo { node_id, address };
156+
157+
let con_node_id = peer_info.node_id;
158+
let con_addr = peer_info.address.clone();
159+
let con_cm = Arc::clone(&self.connection_manager);
160+
161+
con_cm.connect_peer_if_necessary(con_node_id, con_addr).await?;
162+
163+
match self.channel_manager.create_channel(
164+
peer_info.node_id,
165+
channel_amount_sats,
166+
push_msat,
167+
user_channel_id,
168+
None,
169+
Some(user_config),
170+
) {
171+
Ok(_) => {
172+
self.peer_store.add_peer(peer_info)?;
173+
},
174+
Err(_) => {
175+
return Err(Error::ChannelCreationFailed);
176+
},
177+
};
178+
let payjoin_uri =
179+
receiver.receive(bitcoin::Amount::from_sat(channel_amount_sats)).await?;
180+
Ok(payjoin_uri)
181+
} else {
182+
Err(Error::PayjoinReceiverUnavailable)
183+
}
184+
}
107185
}

‎src/wallet.rs

+12
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,18 @@ where
113113
res
114114
}
115115

116+
// Returns the total value of all outputs in the given transaction that are directed to us
117+
pub(crate) fn funds_directed_to_us(&self, tx: &Transaction) -> Result<bitcoin::Amount, Error> {
118+
let locked_wallet = self.inner.lock().unwrap();
119+
let total_value = tx.output.iter().fold(0, |acc, output| {
120+
match locked_wallet.is_mine(&output.script_pubkey) {
121+
Ok(true) => acc + output.value,
122+
_ => acc,
123+
}
124+
});
125+
Ok(bitcoin::Amount::from_sat(total_value))
126+
}
127+
116128
pub(crate) fn build_payjoin_transaction(
117129
&self, output_script: ScriptBuf, value_sats: u64, fee_rate: Option<FeeRate>,
118130
) -> Result<Psbt, Error> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
mod common;
2+
3+
use ldk_node::Event;
4+
use std::time::Duration;
5+
6+
use crate::common::{
7+
expect_channel_pending_event, expect_channel_ready_event, generate_blocks_and_wait,
8+
premine_and_distribute_funds, setup_two_payjoin_nodes, wait_for_tx,
9+
};
10+
use bitcoin::Amount;
11+
use common::setup_bitcoind_and_electrsd;
12+
use tokio::time::sleep;
13+
14+
#[test]
15+
fn send_receive_payjoin_transaction_with_channel_opening() {
16+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
17+
let (node_a_pj_receiver, node_b_pj_sender) = setup_two_payjoin_nodes(&electrsd, false);
18+
let addr_b = node_b_pj_sender.onchain_payment().new_address().unwrap();
19+
let addr_a = node_a_pj_receiver.onchain_payment().new_address().unwrap();
20+
let premine_amount_sat = 100_000_00;
21+
premine_and_distribute_funds(
22+
&bitcoind.client,
23+
&electrsd.client,
24+
vec![addr_b, addr_a],
25+
Amount::from_sat(premine_amount_sat),
26+
);
27+
node_a_pj_receiver.sync_wallets().unwrap();
28+
node_b_pj_sender.sync_wallets().unwrap();
29+
assert_eq!(node_b_pj_sender.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
30+
assert_eq!(node_a_pj_receiver.list_balances().spendable_onchain_balance_sats, 100_000_00);
31+
assert_eq!(node_a_pj_receiver.next_event(), None);
32+
assert_eq!(node_a_pj_receiver.list_channels().len(), 0);
33+
let payjoin_payment = node_a_pj_receiver.payjoin_payment();
34+
let node_b_listening_address =
35+
node_b_pj_sender.listening_addresses().unwrap().get(0).unwrap().clone();
36+
tokio::runtime::Runtime::new().unwrap().handle().block_on(async {
37+
let payjoin_uri = payjoin_payment
38+
.receive_with_channel_opening(
39+
80_000,
40+
None,
41+
false,
42+
node_b_pj_sender.node_id(),
43+
node_b_listening_address,
44+
)
45+
.await
46+
.unwrap();
47+
let payjoin_uri = payjoin_uri.to_string();
48+
dbg!(&payjoin_uri);
49+
let txid = node_b_pj_sender
50+
.payjoin_payment()
51+
.send(payjoin::Uri::try_from(payjoin_uri).unwrap(), None, None)
52+
.await
53+
.unwrap();
54+
if txid.is_none() {
55+
dbg!("no txid yet");
56+
loop {
57+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6);
58+
node_a_pj_receiver.sync_wallets().unwrap();
59+
let node_a_balance = node_a_pj_receiver.list_balances();
60+
if node_a_balance.total_onchain_balance_sats == 80000 + 100_000_00 {
61+
break;
62+
}
63+
}
64+
} else {
65+
dbg!("got txid already");
66+
wait_for_tx(&electrsd.client, txid.unwrap());
67+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6);
68+
}
69+
node_b_pj_sender.sync_wallets().unwrap();
70+
let node_b_balance = node_b_pj_sender.list_balances();
71+
assert!(node_b_balance.total_onchain_balance_sats < premine_amount_sat - 80000);
72+
73+
expect_channel_pending_event!(node_a_pj_receiver, node_b_pj_sender.node_id());
74+
expect_channel_pending_event!(node_b_pj_sender, node_a_pj_receiver.node_id());
75+
let channels = node_a_pj_receiver.list_channels();
76+
let channel = channels.get(0).unwrap();
77+
wait_for_tx(&electrsd.client, channel.funding_txo.unwrap().txid);
78+
let _ = sleep(Duration::from_secs(1));
79+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6);
80+
node_a_pj_receiver.sync_wallets().unwrap();
81+
node_b_pj_sender.sync_wallets().unwrap();
82+
expect_channel_ready_event!(node_a_pj_receiver, node_b_pj_sender.node_id());
83+
expect_channel_ready_event!(node_b_pj_sender, node_a_pj_receiver.node_id());
84+
let channels = node_a_pj_receiver.list_channels();
85+
let channel = channels.get(0).unwrap();
86+
assert_eq!(channel.channel_value_sats, 80_000);
87+
assert!(channel.is_channel_ready);
88+
assert!(channel.is_usable);
89+
90+
assert_eq!(node_a_pj_receiver.list_peers().get(0).unwrap().is_connected, true);
91+
assert_eq!(node_a_pj_receiver.list_peers().get(0).unwrap().is_persisted, true);
92+
assert_eq!(
93+
node_a_pj_receiver.list_peers().get(0).unwrap().node_id,
94+
node_b_pj_sender.node_id()
95+
);
96+
97+
let invoice_amount_1_msat = 2500_000;
98+
let invoice =
99+
node_b_pj_sender.bolt11_payment().receive(invoice_amount_1_msat, "test", 1000).unwrap();
100+
assert!(node_a_pj_receiver.bolt11_payment().send(&invoice).is_ok());
101+
})
102+
}

0 commit comments

Comments
 (0)
Please sign in to comment.