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 b64d6fc

Browse files
committedJul 19, 2024
tests
1 parent 42b8a68 commit b64d6fc

File tree

3 files changed

+237
-0
lines changed

3 files changed

+237
-0
lines changed
 

‎Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ bitcoincore-rpc = { version = "0.17.0", default-features = false }
8686
proptest = "1.0.0"
8787
regex = "1.5.6"
8888

89+
payjoin = { version = "0.16.0", default-features = false, features = ["send", "v2", "receive"] }
90+
reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls", "blocking"] }
91+
8992
[target.'cfg(not(no_download))'.dev-dependencies]
9093
electrsd = { version = "0.26.0", features = ["legacy", "esplora_a33e97e1", "bitcoind_25_0"] }
9194

‎tests/common/mod.rs

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

148148
pub(crate) use expect_payment_successful_event;
149149

150+
macro_rules! expect_payjoin_tx_pending_event {
151+
($node: expr) => {{
152+
match $node.wait_next_event() {
153+
ref e @ Event::PayjoinPaymentPending { txid, .. } => {
154+
println!("{} got event {:?}", $node.node_id(), e);
155+
$node.event_handled();
156+
txid
157+
},
158+
ref e => {
159+
panic!("{} got unexpected event!: {:?}", std::stringify!($node), e);
160+
},
161+
}
162+
}};
163+
}
164+
165+
pub(crate) use expect_payjoin_tx_pending_event;
166+
167+
macro_rules! expect_payjoin_tx_sent_successfully_event {
168+
($node: expr) => {{
169+
match $node.wait_next_event() {
170+
ref e @ Event::PayjoinPaymentSuccessful { 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_tx_sent_successfully_event;
183+
184+
macro_rules! expect_payjoin_tx_broadcasted {
185+
($node: expr) => {{
186+
match $node.wait_next_event() {
187+
ref e @ Event::PayjoinPaymentBroadcasted { txid, .. } => {
188+
println!("{} got event {:?}", $node.node_id(), e);
189+
$node.event_handled();
190+
txid
191+
},
192+
ref e => {
193+
panic!("{} got unexpected event!: {:?}", std::stringify!($node), e);
194+
},
195+
}
196+
}};
197+
}
198+
199+
pub(crate) use expect_payjoin_tx_broadcasted;
200+
150201
pub(crate) fn setup_bitcoind_and_electrsd() -> (BitcoinD, ElectrsD) {
151202
let bitcoind_exe =
152203
env::var("BITCOIND_EXE").ok().or_else(|| bitcoind::downloaded_exe_path().ok()).expect(
@@ -270,6 +321,20 @@ pub(crate) fn setup_node(electrsd: &ElectrsD, config: Config) -> TestNode {
270321
node
271322
}
272323

324+
pub(crate) fn setup_payjoin_node(electrsd: &ElectrsD, config: Config) -> TestNode {
325+
let esplora_url = format!("http://{}", electrsd.esplora_url.as_ref().unwrap());
326+
setup_builder!(builder, config);
327+
builder.set_esplora_server(esplora_url.clone());
328+
let payjoin_relay = "https://pj.bobspacebkk.com".to_string();
329+
builder.set_payjoin_config(payjoin_relay).unwrap();
330+
let test_sync_store = Arc::new(TestSyncStore::new(config.storage_dir_path.into()));
331+
let node = builder.build_with_store(test_sync_store).unwrap();
332+
node.start().unwrap();
333+
assert!(node.status().is_running);
334+
assert!(node.status().latest_fee_rate_cache_update_timestamp.is_some());
335+
node
336+
}
337+
273338
pub(crate) fn generate_blocks_and_wait<E: ElectrumApi>(
274339
bitcoind: &BitcoindClient, electrs: &E, num: usize,
275340
) {

‎tests/integration_tests_payjoin.rs

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
mod common;
2+
3+
use common::{
4+
expect_payjoin_tx_sent_successfully_event, generate_blocks_and_wait,
5+
premine_and_distribute_funds, setup_bitcoind_and_electrsd, wait_for_tx,
6+
};
7+
8+
use bitcoin::Amount;
9+
use ldk_node::{
10+
payment::{PaymentDirection, PaymentKind, PaymentStatus},
11+
Event,
12+
};
13+
use payjoin::{
14+
receive::v2::{Enrolled, Enroller},
15+
OhttpKeys, PjUriBuilder,
16+
};
17+
18+
use crate::common::{
19+
expect_payjoin_tx_broadcasted, expect_payjoin_tx_pending_event, random_config, setup_payjoin_node, setup_two_nodes
20+
};
21+
22+
struct PayjoinReceiver {
23+
ohttp_keys: OhttpKeys,
24+
enrolled: Enrolled,
25+
}
26+
27+
impl PayjoinReceiver {
28+
fn enroll() -> Self {
29+
let payjoin_directory = payjoin::Url::parse("https://payjo.in").unwrap();
30+
let payjoin_relay = payjoin::Url::parse("https://pj.bobspacebkk.com").unwrap();
31+
let ohttp_keys = {
32+
let payjoin_directory = payjoin_directory.join("/ohttp-keys").unwrap();
33+
let proxy = reqwest::Proxy::all(payjoin_relay.clone()).unwrap();
34+
let client = reqwest::blocking::Client::builder().proxy(proxy).build().unwrap();
35+
let response = client.get(payjoin_directory).send().unwrap();
36+
let response = response.bytes().unwrap();
37+
OhttpKeys::decode(response.to_vec().as_slice()).unwrap()
38+
};
39+
dbg!(&ohttp_keys);
40+
let mut enroller = Enroller::from_directory_config(
41+
payjoin_directory.clone(),
42+
ohttp_keys.clone(),
43+
payjoin_relay.clone(),
44+
);
45+
let (req, ctx) = enroller.extract_req().unwrap();
46+
let mut headers = reqwest::header::HeaderMap::new();
47+
headers.insert(
48+
reqwest::header::CONTENT_TYPE,
49+
reqwest::header::HeaderValue::from_static("message/ohttp-req"),
50+
);
51+
let response = reqwest::blocking::Client::new()
52+
.post(&req.url.to_string())
53+
.body(req.body)
54+
.headers(headers)
55+
.send()
56+
.unwrap();
57+
let response = match response.bytes() {
58+
Ok(response) => response,
59+
Err(_) => {
60+
panic!("Error reading response");
61+
},
62+
};
63+
let enrolled = enroller.process_res(response.to_vec().as_slice(), ctx).unwrap();
64+
Self { ohttp_keys, enrolled }
65+
}
66+
67+
pub(crate) fn receive(
68+
&self, amount: bitcoin::Amount, receiving_address: bitcoin::Address,
69+
) -> String {
70+
let enrolled = self.enrolled.clone();
71+
let fallback_target = enrolled.fallback_target();
72+
let ohttp_keys = self.ohttp_keys.clone();
73+
let pj_part = payjoin::Url::parse(&fallback_target).unwrap();
74+
let payjoin_uri = PjUriBuilder::new(receiving_address, pj_part, Some(ohttp_keys.clone()))
75+
.amount(amount)
76+
.build();
77+
payjoin_uri.to_string()
78+
}
79+
80+
pub(crate) fn process_payjoin_request(self) {
81+
let mut enrolled = self.enrolled;
82+
let (req, context) = enrolled.extract_req().unwrap();
83+
84+
let client = reqwest::blocking::Client::new();
85+
let response = client
86+
.post(req.url.to_string())
87+
.body(req.body)
88+
.headers(PayjoinReceiver::ohttp_headers())
89+
.send()
90+
.unwrap();
91+
let response = response.bytes().unwrap();
92+
let response = enrolled.process_res(response.to_vec().as_slice(), context).unwrap();
93+
let unchecked_proposal = response.unwrap();
94+
95+
let proposal = unchecked_proposal.assume_interactive_receiver();
96+
let proposal = proposal.check_inputs_not_owned(|_script| Ok(false)).unwrap();
97+
let proposal = proposal.check_no_mixed_input_scripts().unwrap();
98+
let proposal = proposal.check_no_inputs_seen_before(|_outpoint| Ok(false)).unwrap();
99+
let provisional_proposal = proposal.identify_receiver_outputs(|_script| Ok(true)).unwrap();
100+
101+
// Finalise Payjoin Proposal
102+
let mut payjoin_proposal =
103+
provisional_proposal.finalize_proposal(|psbt| Ok(psbt.clone()), None).unwrap();
104+
105+
let (receiver_request, _) = payjoin_proposal.extract_v2_req().unwrap();
106+
reqwest::blocking::Client::new()
107+
.post(&receiver_request.url.to_string())
108+
.body(receiver_request.body)
109+
.headers(PayjoinReceiver::ohttp_headers())
110+
.send()
111+
.unwrap();
112+
}
113+
114+
fn ohttp_headers() -> reqwest::header::HeaderMap {
115+
let mut headers = reqwest::header::HeaderMap::new();
116+
headers.insert(
117+
reqwest::header::CONTENT_TYPE,
118+
reqwest::header::HeaderValue::from_static("message/ohttp-req"),
119+
);
120+
headers
121+
}
122+
}
123+
124+
#[test]
125+
fn send_receive_regular_payjoin_transaction() {
126+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
127+
let config_a = random_config(false);
128+
let (receiver, _) = setup_two_nodes(&electrsd, false, false, false);
129+
let payjoin_sender = setup_payjoin_node(&electrsd, config_a);
130+
let addr_a = payjoin_sender.onchain_payment().new_address().unwrap();
131+
let premine_amount_sat = 100_000_00;
132+
premine_and_distribute_funds(
133+
&bitcoind.client,
134+
&electrsd.client,
135+
vec![addr_a],
136+
Amount::from_sat(premine_amount_sat),
137+
);
138+
payjoin_sender.sync_wallets().unwrap();
139+
assert_eq!(payjoin_sender.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
140+
assert_eq!(payjoin_sender.list_balances().spendable_onchain_balance_sats, 100_000_00);
141+
assert_eq!(payjoin_sender.next_event(), None);
142+
143+
let payjoin_receiver = PayjoinReceiver::enroll();
144+
let payjoin_uri = payjoin_receiver
145+
.receive(Amount::from_sat(80_000), receiver.onchain_payment().new_address().unwrap());
146+
dbg!(&payjoin_uri);
147+
148+
assert!(payjoin_sender.payjoin_payment().send(payjoin_uri).is_ok());
149+
150+
let txid = expect_payjoin_tx_pending_event!(payjoin_sender);
151+
let payments = payjoin_sender.list_payments();
152+
let payment = payments.first().unwrap();
153+
assert_eq!(payment.amount_msat, Some(80_000));
154+
assert_eq!(payment.status, PaymentStatus::Pending);
155+
assert_eq!(payment.direction, PaymentDirection::Outbound);
156+
assert_eq!(payment.kind, PaymentKind::Payjoin);
157+
158+
payjoin_receiver.process_payjoin_request();
159+
160+
expect_payjoin_tx_broadcasted!(payjoin_sender);
161+
162+
wait_for_tx(&electrsd.client, txid);
163+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 3);
164+
payjoin_sender.sync_wallets().unwrap();
165+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 4);
166+
payjoin_sender.sync_wallets().unwrap();
167+
168+
expect_payjoin_tx_sent_successfully_event!(payjoin_sender);
169+
}

0 commit comments

Comments
 (0)
Please sign in to comment.