Skip to content

Commit 9c6b018

Browse files
committed
Expose PayjoinPayment module
1 parent 3b9150f commit 9c6b018

File tree

6 files changed

+443
-4
lines changed

6 files changed

+443
-4
lines changed

Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ electrum-client = { version = "0.15.1", default-features = true }
8585
bitcoincore-rpc = { version = "0.17.0", default-features = false }
8686
proptest = "1.0.0"
8787
regex = "1.5.6"
88+
payjoin = { version = "0.16.0", default-features = false, features = ["send", "v2", "receive"] }
89+
reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls", "blocking"] }
8890

8991
[target.'cfg(not(no_download))'.dev-dependencies]
9092
electrsd = { version = "0.26.0", features = ["legacy", "esplora_a33e97e1", "bitcoind_25_0"] }

bindings/ldk_node.udl

+27
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ interface Node {
6464
SpontaneousPayment spontaneous_payment();
6565
OnchainPayment onchain_payment();
6666
UnifiedQrPayment unified_qr_payment();
67+
PayjoinPayment payjoin_payment();
6768
[Throws=NodeError]
6869
void connect(PublicKey node_id, SocketAddress address, boolean persist);
6970
[Throws=NodeError]
@@ -156,6 +157,13 @@ interface UnifiedQrPayment {
156157
QrPaymentResult send([ByRef]string uri_str);
157158
};
158159

160+
interface PayjoinPayment {
161+
[Throws=NodeError]
162+
void send(string payjoin_uri);
163+
[Throws=NodeError]
164+
void send_with_amount(string payjoin_uri, u64 amount_sats);
165+
};
166+
159167
[Error]
160168
enum NodeError {
161169
"AlreadyRunning",
@@ -206,6 +214,12 @@ enum NodeError {
206214
"InsufficientFunds",
207215
"LiquiditySourceUnavailable",
208216
"LiquidityFeeTooHigh",
217+
"PayjoinUnavailable",
218+
"PayjoinUriInvalid",
219+
"PayjoinRequestMissingAmount",
220+
"PayjoinRequestCreationFailed",
221+
"PayjoinRequestSendingFailed",
222+
"PayjoinResponseProcessingFailed",
209223
};
210224

211225
dictionary NodeStatus {
@@ -231,6 +245,7 @@ enum BuildError {
231245
"InvalidSystemTime",
232246
"InvalidChannelMonitor",
233247
"InvalidListeningAddresses",
248+
"InvalidPayjoinConfig",
234249
"ReadFailed",
235250
"WriteFailed",
236251
"StoragePathAccessFailed",
@@ -248,6 +263,9 @@ interface Event {
248263
ChannelPending(ChannelId channel_id, UserChannelId user_channel_id, ChannelId former_temporary_channel_id, PublicKey counterparty_node_id, OutPoint funding_txo);
249264
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id);
250265
ChannelClosed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, ClosureReason? reason);
266+
PayjoinPaymentAwaitingConfirmation(Txid txid, u64 amount_sats);
267+
PayjoinPaymentSuccessful(Txid txid, u64 amount_sats, boolean is_original_psbt_modified);
268+
PayjoinPaymentFailed(Txid txid, u64 amount_sats, PayjoinPaymentFailureReason reason);
251269
};
252270

253271
enum PaymentFailureReason {
@@ -259,6 +277,12 @@ enum PaymentFailureReason {
259277
"UnexpectedError",
260278
};
261279

280+
enum PayjoinPaymentFailureReason {
281+
"Timeout",
282+
"RequestSendingFailed",
283+
"ResponseProcessingFailed",
284+
};
285+
262286
[Enum]
263287
interface ClosureReason {
264288
CounterpartyForceClosed(UntrustedString peer_msg);
@@ -284,6 +308,7 @@ interface PaymentKind {
284308
Bolt12Offer(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, OfferId offer_id);
285309
Bolt12Refund(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret);
286310
Spontaneous(PaymentHash hash, PaymentPreimage? preimage);
311+
Payjoin();
287312
};
288313

289314
[Enum]
@@ -316,6 +341,8 @@ dictionary PaymentDetails {
316341
PaymentDirection direction;
317342
PaymentStatus status;
318343
u64 latest_update_timestamp;
344+
Txid? txid;
345+
BestBlock? best_block;
319346
};
320347

321348
[NonExhaustive]

src/builder.rs

+13
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::types::{
1717
MessageRouter, OnionMessenger, PaymentStore, PeerManager,
1818
};
1919
use crate::wallet::Wallet;
20+
use crate::PayjoinHandler;
2021
use crate::{LogLevel, Node};
2122

2223
use lightning::chain::{chainmonitor, BestBlock, Watch};
@@ -994,6 +995,17 @@ fn build_with_store_internal(
994995
let (stop_sender, _) = tokio::sync::watch::channel(());
995996
let (event_handling_stopped_sender, _) = tokio::sync::watch::channel(());
996997

998+
let payjoin_handler = payjoin_config.map(|pj_config| {
999+
Arc::new(PayjoinHandler::new(
1000+
Arc::clone(&tx_sync),
1001+
Arc::clone(&event_queue),
1002+
Arc::clone(&logger),
1003+
pj_config.payjoin_relay.clone(),
1004+
Arc::clone(&payment_store),
1005+
Arc::clone(&wallet),
1006+
))
1007+
});
1008+
9971009
let is_listening = Arc::new(AtomicBool::new(false));
9981010
let latest_wallet_sync_timestamp = Arc::new(RwLock::new(None));
9991011
let latest_onchain_wallet_sync_timestamp = Arc::new(RwLock::new(None));
@@ -1015,6 +1027,7 @@ fn build_with_store_internal(
10151027
channel_manager,
10161028
chain_monitor,
10171029
output_sweeper,
1030+
payjoin_handler,
10181031
peer_manager,
10191032
connection_manager,
10201033
keys_manager,

src/lib.rs

+53-4
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,10 @@ pub use config::{default_config, AnchorChannelsConfig, Config};
108108
pub use error::Error as NodeError;
109109
use error::Error;
110110

111+
#[cfg(feature = "uniffi")]
112+
use crate::event::PayjoinPaymentFailureReason;
111113
pub use event::Event;
114+
use payment::payjoin::handler::PayjoinHandler;
112115
pub use types::ChannelConfig;
113116

114117
pub use io::utils::generate_entropy_mnemonic;
@@ -133,8 +136,8 @@ use gossip::GossipSource;
133136
use graph::NetworkGraph;
134137
use liquidity::LiquiditySource;
135138
use payment::{
136-
Bolt11Payment, Bolt12Payment, OnchainPayment, PaymentDetails, SpontaneousPayment,
137-
UnifiedQrPayment,
139+
Bolt11Payment, Bolt12Payment, OnchainPayment, PayjoinPayment, PaymentDetails,
140+
SpontaneousPayment, UnifiedQrPayment,
138141
};
139142
use peer_store::{PeerInfo, PeerStore};
140143
use types::{
@@ -187,6 +190,7 @@ pub struct Node {
187190
output_sweeper: Arc<Sweeper>,
188191
peer_manager: Arc<PeerManager>,
189192
connection_manager: Arc<ConnectionManager<Arc<FilesystemLogger>>>,
193+
payjoin_handler: Option<Arc<PayjoinHandler>>,
190194
keys_manager: Arc<KeysManager>,
191195
network_graph: Arc<Graph>,
192196
gossip_source: Arc<GossipSource>,
@@ -379,6 +383,8 @@ impl Node {
379383
let archive_cmon = Arc::clone(&self.chain_monitor);
380384
let sync_sweeper = Arc::clone(&self.output_sweeper);
381385
let sync_logger = Arc::clone(&self.logger);
386+
let sync_payjoin = &self.payjoin_handler.as_ref();
387+
let sync_payjoin = sync_payjoin.map(Arc::clone);
382388
let sync_wallet_timestamp = Arc::clone(&self.latest_wallet_sync_timestamp);
383389
let sync_monitor_archival_height = Arc::clone(&self.latest_channel_monitor_archival_height);
384390
let mut stop_sync = self.stop_sender.subscribe();
@@ -398,11 +404,14 @@ impl Node {
398404
return;
399405
}
400406
_ = wallet_sync_interval.tick() => {
401-
let confirmables = vec![
407+
let mut confirmables = vec![
402408
&*sync_cman as &(dyn Confirm + Sync + Send),
403409
&*sync_cmon as &(dyn Confirm + Sync + Send),
404410
&*sync_sweeper as &(dyn Confirm + Sync + Send),
405411
];
412+
if let Some(sync_payjoin) = sync_payjoin.as_ref() {
413+
confirmables.push(sync_payjoin.as_ref() as &(dyn Confirm + Sync + Send));
414+
}
406415
let now = Instant::now();
407416
let timeout_fut = tokio::time::timeout(Duration::from_secs(LDK_WALLET_SYNC_TIMEOUT_SECS), tx_sync.sync(confirmables));
408417
match timeout_fut.await {
@@ -1108,6 +1117,42 @@ impl Node {
11081117
))
11091118
}
11101119

1120+
/// Returns a Payjoin payment handler allowing to send Payjoin transactions
1121+
///
1122+
/// in order to utilize Payjoin functionality, it is necessary to configure a Payjoin relay
1123+
/// using [`set_payjoin_config`].
1124+
///
1125+
/// [`set_payjoin_config`]: crate::builder::NodeBuilder::set_payjoin_config
1126+
#[cfg(not(feature = "uniffi"))]
1127+
pub fn payjoin_payment(&self) -> PayjoinPayment {
1128+
let payjoin_handler = self.payjoin_handler.as_ref();
1129+
PayjoinPayment::new(
1130+
Arc::clone(&self.config),
1131+
Arc::clone(&self.logger),
1132+
payjoin_handler.map(Arc::clone),
1133+
Arc::clone(&self.runtime),
1134+
Arc::clone(&self.tx_broadcaster),
1135+
)
1136+
}
1137+
1138+
/// Returns a Payjoin payment handler allowing to send Payjoin transactions.
1139+
///
1140+
/// in order to utilize Payjoin functionality, it is necessary to configure a Payjoin relay
1141+
/// using [`set_payjoin_config`].
1142+
///
1143+
/// [`set_payjoin_config`]: crate::builder::NodeBuilder::set_payjoin_config
1144+
#[cfg(feature = "uniffi")]
1145+
pub fn payjoin_payment(&self) -> Arc<PayjoinPayment> {
1146+
let payjoin_handler = self.payjoin_handler.as_ref();
1147+
Arc::new(PayjoinPayment::new(
1148+
Arc::clone(&self.config),
1149+
Arc::clone(&self.logger),
1150+
payjoin_handler.map(Arc::clone),
1151+
Arc::clone(&self.runtime),
1152+
Arc::clone(&self.tx_broadcaster),
1153+
))
1154+
}
1155+
11111156
/// Retrieve a list of known channels.
11121157
pub fn list_channels(&self) -> Vec<ChannelDetails> {
11131158
self.channel_manager.list_channels().into_iter().map(|c| c.into()).collect()
@@ -1309,11 +1354,15 @@ impl Node {
13091354
let fee_estimator = Arc::clone(&self.fee_estimator);
13101355
let sync_sweeper = Arc::clone(&self.output_sweeper);
13111356
let sync_logger = Arc::clone(&self.logger);
1312-
let confirmables = vec![
1357+
let sync_payjoin = &self.payjoin_handler.as_ref();
1358+
let mut confirmables = vec![
13131359
&*sync_cman as &(dyn Confirm + Sync + Send),
13141360
&*sync_cmon as &(dyn Confirm + Sync + Send),
13151361
&*sync_sweeper as &(dyn Confirm + Sync + Send),
13161362
];
1363+
if let Some(sync_payjoin) = sync_payjoin {
1364+
confirmables.push(sync_payjoin.as_ref() as &(dyn Confirm + Sync + Send));
1365+
}
13171366
let sync_wallet_timestamp = Arc::clone(&self.latest_wallet_sync_timestamp);
13181367
let sync_fee_rate_update_timestamp =
13191368
Arc::clone(&self.latest_fee_rate_cache_update_timestamp);

tests/common/mod.rs

+49
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,41 @@ macro_rules! expect_payment_successful_event {
147147

148148
pub(crate) use expect_payment_successful_event;
149149

150+
macro_rules! expect_payjoin_tx_sent_successfully_event {
151+
($node: expr, $is_original_psbt_modified: expr) => {{
152+
match $node.wait_next_event() {
153+
ref e @ Event::PayjoinPaymentSuccessful { txid, is_original_psbt_modified, .. } => {
154+
println!("{} got event {:?}", $node.node_id(), e);
155+
assert_eq!(is_original_psbt_modified, $is_original_psbt_modified);
156+
$node.event_handled();
157+
txid
158+
},
159+
ref e => {
160+
panic!("{} got unexpected event!: {:?}", std::stringify!($node), e);
161+
},
162+
}
163+
}};
164+
}
165+
166+
pub(crate) use expect_payjoin_tx_sent_successfully_event;
167+
168+
macro_rules! expect_payjoin_await_confirmation {
169+
($node: expr) => {{
170+
match $node.wait_next_event() {
171+
ref e @ Event::PayjoinPaymentAwaitingConfirmation { txid, .. } => {
172+
println!("{} got event {:?}", $node.node_id(), e);
173+
$node.event_handled();
174+
txid
175+
},
176+
ref e => {
177+
panic!("{} got unexpected event!: {:?}", std::stringify!($node), e);
178+
},
179+
}
180+
}};
181+
}
182+
183+
pub(crate) use expect_payjoin_await_confirmation;
184+
150185
pub(crate) fn setup_bitcoind_and_electrsd() -> (BitcoinD, ElectrsD) {
151186
let bitcoind_exe =
152187
env::var("BITCOIND_EXE").ok().or_else(|| bitcoind::downloaded_exe_path().ok()).expect(
@@ -270,6 +305,20 @@ pub(crate) fn setup_node(electrsd: &ElectrsD, config: Config) -> TestNode {
270305
node
271306
}
272307

308+
pub(crate) fn setup_payjoin_node(electrsd: &ElectrsD, config: Config) -> TestNode {
309+
let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap());
310+
setup_builder!(builder, config);
311+
builder.set_esplora_server(esplora_url.clone());
312+
let payjoin_relay = "https://pj.bobspacebkk.com".to_string();
313+
builder.set_payjoin_config(payjoin_relay).unwrap();
314+
let test_sync_store = Arc::new(TestSyncStore::new(config.storage_dir_path.into()));
315+
let node = builder.build_with_store(test_sync_store).unwrap();
316+
node.start().unwrap();
317+
assert!(node.status().is_running);
318+
assert!(node.status().latest_fee_rate_cache_update_timestamp.is_some());
319+
node
320+
}
321+
273322
pub(crate) fn generate_blocks_and_wait<E: ElectrumApi>(
274323
bitcoind: &BitcoindClient, electrs: &E, num: usize,
275324
) {

0 commit comments

Comments
 (0)