Skip to content

Commit

Permalink
Expose PayjoinPayment module
Browse files Browse the repository at this point in the history
  • Loading branch information
jbesraa committed Jul 24, 2024
1 parent a532101 commit eaaac75
Show file tree
Hide file tree
Showing 6 changed files with 436 additions and 4 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ electrum-client = { version = "0.15.1", default-features = true }
bitcoincore-rpc = { version = "0.17.0", default-features = false }
proptest = "1.0.0"
regex = "1.5.6"
payjoin = { version = "0.16.0", default-features = false, features = ["send", "v2", "receive"] }
reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls", "blocking"] }

[target.'cfg(not(no_download))'.dev-dependencies]
electrsd = { version = "0.26.0", features = ["legacy", "esplora_a33e97e1", "bitcoind_25_0"] }
Expand Down
29 changes: 29 additions & 0 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ interface Node {
SpontaneousPayment spontaneous_payment();
OnchainPayment onchain_payment();
UnifiedQrPayment unified_qr_payment();
PayjoinPayment payjoin_payment();
[Throws=NodeError]
void connect(PublicKey node_id, SocketAddress address, boolean persist);
[Throws=NodeError]
Expand Down Expand Up @@ -156,6 +157,13 @@ interface UnifiedQrPayment {
QrPaymentResult send([ByRef]string uri_str);
};

interface PayjoinPayment {
[Throws=NodeError]
void send(string payjoin_uri);
[Throws=NodeError]
void send_with_amount(string payjoin_uri, u64 amount_sats);
};

[Error]
enum NodeError {
"AlreadyRunning",
Expand Down Expand Up @@ -206,6 +214,12 @@ enum NodeError {
"InsufficientFunds",
"LiquiditySourceUnavailable",
"LiquidityFeeTooHigh",
"PayjoinUnavailable",
"PayjoinUriInvalid",
"PayjoinRequestMissingAmount",
"PayjoinRequestCreationFailed",
"PayjoinRequestSendingFailed",
"PayjoinResponseProcessingFailed",
};

dictionary NodeStatus {
Expand All @@ -231,6 +245,7 @@ enum BuildError {
"InvalidSystemTime",
"InvalidChannelMonitor",
"InvalidListeningAddresses",
"InvalidPayjoinConfig",
"ReadFailed",
"WriteFailed",
"StoragePathAccessFailed",
Expand All @@ -248,6 +263,11 @@ interface Event {
ChannelPending(ChannelId channel_id, UserChannelId user_channel_id, ChannelId former_temporary_channel_id, PublicKey counterparty_node_id, OutPoint funding_txo);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id);
ChannelClosed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, ClosureReason? reason);
PayjoinPaymentPending(Txid txid, u64 amount_sats);
PayjoinPaymentBroadcasted(Txid txid, u64 amount_sats);
PayjoinPaymentSuccessful(Txid txid, u64 amount_sats, boolean is_original_psbt_modified);
PayjoinPaymentFailed(Txid txid, u64 amount_sats, PayjoinPaymentFailureReason reason);
PayjoinPaymentOriginalPsbtBroadcasted(Txid txid, u64 amount_sats);
};

enum PaymentFailureReason {
Expand All @@ -259,6 +279,12 @@ enum PaymentFailureReason {
"UnexpectedError",
};

enum PayjoinPaymentFailureReason {
"Timeout",
"RequestSendingFailed",
"ResponseProcessingFailed",
};

[Enum]
interface ClosureReason {
CounterpartyForceClosed(UntrustedString peer_msg);
Expand All @@ -284,6 +310,7 @@ interface PaymentKind {
Bolt12Offer(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret, OfferId offer_id);
Bolt12Refund(PaymentHash? hash, PaymentPreimage? preimage, PaymentSecret? secret);
Spontaneous(PaymentHash hash, PaymentPreimage? preimage);
Payjoin();
};

[Enum]
Expand Down Expand Up @@ -316,6 +343,8 @@ dictionary PaymentDetails {
PaymentDirection direction;
PaymentStatus status;
u64 latest_update_timestamp;
Txid? txid;
BestBlock? best_block;
};

[NonExhaustive]
Expand Down
13 changes: 13 additions & 0 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::types::{
MessageRouter, OnionMessenger, PaymentStore, PeerManager,
};
use crate::wallet::Wallet;
use crate::PayjoinHandler;
use crate::{LogLevel, Node};

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

let payjoin_handler = payjoin_config.map(|pj_config| {
Arc::new(PayjoinHandler::new(
Arc::clone(&tx_sync),
Arc::clone(&event_queue),
Arc::clone(&logger),
pj_config.payjoin_relay.clone(),
Arc::clone(&payment_store),
Arc::clone(&wallet),
))
});

let is_listening = Arc::new(AtomicBool::new(false));
let latest_wallet_sync_timestamp = Arc::new(RwLock::new(None));
let latest_onchain_wallet_sync_timestamp = Arc::new(RwLock::new(None));
Expand All @@ -1015,6 +1027,7 @@ fn build_with_store_internal(
channel_manager,
chain_monitor,
output_sweeper,
payjoin_handler,
peer_manager,
connection_manager,
keys_manager,
Expand Down
57 changes: 53 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ pub use config::{default_config, AnchorChannelsConfig, Config};
pub use error::Error as NodeError;
use error::Error;

#[cfg(feature = "uniffi")]
use crate::event::PayjoinPaymentFailureReason;
pub use event::Event;
use payment::payjoin::handler::PayjoinHandler;
pub use types::ChannelConfig;

pub use io::utils::generate_entropy_mnemonic;
Expand All @@ -133,8 +136,8 @@ use gossip::GossipSource;
use graph::NetworkGraph;
use liquidity::LiquiditySource;
use payment::{
Bolt11Payment, Bolt12Payment, OnchainPayment, PaymentDetails, SpontaneousPayment,
UnifiedQrPayment,
Bolt11Payment, Bolt12Payment, OnchainPayment, PayjoinPayment, PaymentDetails,
SpontaneousPayment, UnifiedQrPayment,
};
use peer_store::{PeerInfo, PeerStore};
use types::{
Expand Down Expand Up @@ -187,6 +190,7 @@ pub struct Node {
output_sweeper: Arc<Sweeper>,
peer_manager: Arc<PeerManager>,
connection_manager: Arc<ConnectionManager<Arc<FilesystemLogger>>>,
payjoin_handler: Option<Arc<PayjoinHandler>>,
keys_manager: Arc<KeysManager>,
network_graph: Arc<Graph>,
gossip_source: Arc<GossipSource>,
Expand Down Expand Up @@ -379,6 +383,8 @@ impl Node {
let archive_cmon = Arc::clone(&self.chain_monitor);
let sync_sweeper = Arc::clone(&self.output_sweeper);
let sync_logger = Arc::clone(&self.logger);
let sync_payjoin = &self.payjoin_handler.as_ref();
let sync_payjoin = sync_payjoin.map(Arc::clone);
let sync_wallet_timestamp = Arc::clone(&self.latest_wallet_sync_timestamp);
let sync_monitor_archival_height = Arc::clone(&self.latest_channel_monitor_archival_height);
let mut stop_sync = self.stop_sender.subscribe();
Expand All @@ -398,11 +404,14 @@ impl Node {
return;
}
_ = wallet_sync_interval.tick() => {
let confirmables = vec![
let mut confirmables = vec![
&*sync_cman as &(dyn Confirm + Sync + Send),
&*sync_cmon as &(dyn Confirm + Sync + Send),
&*sync_sweeper as &(dyn Confirm + Sync + Send),
];
if let Some(sync_payjoin) = sync_payjoin.as_ref() {
confirmables.push(sync_payjoin.as_ref() as &(dyn Confirm + Sync + Send));
}
let now = Instant::now();
let timeout_fut = tokio::time::timeout(Duration::from_secs(LDK_WALLET_SYNC_TIMEOUT_SECS), tx_sync.sync(confirmables));
match timeout_fut.await {
Expand Down Expand Up @@ -1108,6 +1117,42 @@ impl Node {
))
}

/// Returns a Payjoin payment handler allowing to send Payjoin transactions
///
/// in order to utilize Payjoin functionality, it is necessary to configure a Payjoin relay
/// using [`set_payjoin_config`].
///
/// [`set_payjoin_config`]: crate::builder::NodeBuilder::set_payjoin_config
#[cfg(not(feature = "uniffi"))]
pub fn payjoin_payment(&self) -> PayjoinPayment {
let payjoin_handler = self.payjoin_handler.as_ref();
PayjoinPayment::new(
Arc::clone(&self.config),
Arc::clone(&self.logger),
payjoin_handler.map(Arc::clone),
Arc::clone(&self.runtime),
Arc::clone(&self.tx_broadcaster),
)
}

/// Returns a Payjoin payment handler allowing to send Payjoin transactions.
///
/// in order to utilize Payjoin functionality, it is necessary to configure a Payjoin relay
/// using [`set_payjoin_config`].
///
/// [`set_payjoin_config`]: crate::builder::NodeBuilder::set_payjoin_config
#[cfg(feature = "uniffi")]
pub fn payjoin_payment(&self) -> Arc<PayjoinPayment> {
let payjoin_handler = self.payjoin_handler.as_ref();
Arc::new(PayjoinPayment::new(
Arc::clone(&self.config),
Arc::clone(&self.logger),
payjoin_handler.map(Arc::clone),
Arc::clone(&self.runtime),
Arc::clone(&self.tx_broadcaster),
))
}

/// Retrieve a list of known channels.
pub fn list_channels(&self) -> Vec<ChannelDetails> {
self.channel_manager.list_channels().into_iter().map(|c| c.into()).collect()
Expand Down Expand Up @@ -1309,11 +1354,15 @@ impl Node {
let fee_estimator = Arc::clone(&self.fee_estimator);
let sync_sweeper = Arc::clone(&self.output_sweeper);
let sync_logger = Arc::clone(&self.logger);
let confirmables = vec![
let sync_payjoin = &self.payjoin_handler.as_ref();
let mut confirmables = vec![
&*sync_cman as &(dyn Confirm + Sync + Send),
&*sync_cmon as &(dyn Confirm + Sync + Send),
&*sync_sweeper as &(dyn Confirm + Sync + Send),
];
if let Some(sync_payjoin) = sync_payjoin {
confirmables.push(sync_payjoin.as_ref() as &(dyn Confirm + Sync + Send));
}
let sync_wallet_timestamp = Arc::clone(&self.latest_wallet_sync_timestamp);
let sync_fee_rate_update_timestamp =
Arc::clone(&self.latest_fee_rate_cache_update_timestamp);
Expand Down
66 changes: 66 additions & 0 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,58 @@ macro_rules! expect_payment_successful_event {

pub(crate) use expect_payment_successful_event;

macro_rules! expect_payjoin_tx_pending_event {
($node: expr) => {{
match $node.wait_next_event() {
ref e @ Event::PayjoinPaymentPending { txid, .. } => {
println!("{} got event {:?}", $node.node_id(), e);
$node.event_handled();
txid
},
ref e => {
panic!("{} got unexpected event!: {:?}", std::stringify!($node), e);
},
}
}};
}

pub(crate) use expect_payjoin_tx_pending_event;

macro_rules! expect_payjoin_tx_sent_successfully_event {
($node: expr, $is_original_psbt_modified: expr) => {{
match $node.wait_next_event() {
ref e @ Event::PayjoinPaymentSuccessful { txid, is_original_psbt_modified, .. } => {
println!("{} got event {:?}", $node.node_id(), e);
assert_eq!(is_original_psbt_modified, $is_original_psbt_modified);
$node.event_handled();
txid
},
ref e => {
panic!("{} got unexpected event!: {:?}", std::stringify!($node), e);
},
}
}};
}

pub(crate) use expect_payjoin_tx_sent_successfully_event;

macro_rules! expect_payjoin_tx_broadcasted {
($node: expr) => {{
match $node.wait_next_event() {
ref e @ Event::PayjoinPaymentBroadcasted { txid, .. } => {
println!("{} got event {:?}", $node.node_id(), e);
$node.event_handled();
txid
},
ref e => {
panic!("{} got unexpected event!: {:?}", std::stringify!($node), e);
},
}
}};
}

pub(crate) use expect_payjoin_tx_broadcasted;

pub(crate) fn setup_bitcoind_and_electrsd() -> (BitcoinD, ElectrsD) {
let bitcoind_exe =
env::var("BITCOIND_EXE").ok().or_else(|| bitcoind::downloaded_exe_path().ok()).expect(
Expand Down Expand Up @@ -270,6 +322,20 @@ pub(crate) fn setup_node(electrsd: &ElectrsD, config: Config) -> TestNode {
node
}

pub(crate) fn setup_payjoin_node(electrsd: &ElectrsD, config: Config) -> TestNode {
let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap());
setup_builder!(builder, config);
builder.set_esplora_server(esplora_url.clone());
let payjoin_relay = "https://pj.bobspacebkk.com".to_string();
builder.set_payjoin_config(payjoin_relay).unwrap();
let test_sync_store = Arc::new(TestSyncStore::new(config.storage_dir_path.into()));
let node = builder.build_with_store(test_sync_store).unwrap();
node.start().unwrap();
assert!(node.status().is_running);
assert!(node.status().latest_fee_rate_cache_update_timestamp.is_some());
node
}

pub(crate) fn generate_blocks_and_wait<E: ElectrumApi>(
bitcoind: &BitcoindClient, electrs: &E, num: usize,
) {
Expand Down
Loading

0 comments on commit eaaac75

Please sign in to comment.