Skip to content

Commit 73f5b8f

Browse files
committed
Add CLN integration tests
1 parent 20d9d76 commit 73f5b8f

File tree

3 files changed

+342
-0
lines changed

3 files changed

+342
-0
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ electrum-client = "0.12.0"
8181
proptest = "1.0.0"
8282
regex = "1.5.6"
8383

84+
[target.'cfg(cln_test)'.dev-dependencies]
85+
clightningrpc = { version = "0.3.0-beta.8", default-features = false }
86+
bitcoincore-rpc = { version = "0.16.0", default-features = false }
87+
8488
[build-dependencies]
8589
uniffi = { version = "0.25.1", features = ["build"], optional = true }
8690

tests/common.rs

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
#![cfg(cln_test)]
2+
3+
use ldk_node::{Config, LogLevel};
4+
5+
use lightning::ln::msgs::SocketAddress;
6+
7+
use bitcoin::{Address, Amount, Network, OutPoint, Txid};
8+
9+
use bitcoincore_rpc::bitcoincore_rpc_json::AddressType;
10+
use bitcoincore_rpc::Client as BitcoindClient;
11+
use bitcoincore_rpc::RpcApi;
12+
13+
use electrum_client::Client as ElectrumClient;
14+
use electrum_client::ElectrumApi;
15+
16+
use rand::distributions::Alphanumeric;
17+
use rand::{thread_rng, Rng};
18+
19+
use std::path::PathBuf;
20+
use std::time::Duration;
21+
22+
macro_rules! expect_event {
23+
($node: expr, $event_type: ident) => {{
24+
match $node.wait_next_event() {
25+
ref e @ Event::$event_type { .. } => {
26+
println!("{} got event {:?}", $node.node_id(), e);
27+
$node.event_handled();
28+
}
29+
ref e => {
30+
panic!("{} got unexpected event!: {:?}", std::stringify!($node), e);
31+
}
32+
}
33+
}};
34+
}
35+
36+
pub(crate) use expect_event;
37+
38+
macro_rules! expect_channel_pending_event {
39+
($node: expr, $counterparty_node_id: expr) => {{
40+
match $node.wait_next_event() {
41+
ref e @ Event::ChannelPending { funding_txo, counterparty_node_id, .. } => {
42+
println!("{} got event {:?}", $node.node_id(), e);
43+
assert_eq!(counterparty_node_id, $counterparty_node_id);
44+
$node.event_handled();
45+
funding_txo
46+
}
47+
ref e => {
48+
panic!("{} got unexpected event!: {:?}", std::stringify!($node), e);
49+
}
50+
}
51+
}};
52+
}
53+
54+
pub(crate) use expect_channel_pending_event;
55+
56+
macro_rules! expect_channel_ready_event {
57+
($node: expr, $counterparty_node_id: expr) => {{
58+
match $node.wait_next_event() {
59+
ref e @ Event::ChannelReady { channel_id, counterparty_node_id, .. } => {
60+
println!("{} got event {:?}", $node.node_id(), e);
61+
assert_eq!(counterparty_node_id, Some($counterparty_node_id));
62+
$node.event_handled();
63+
channel_id
64+
}
65+
ref e => {
66+
panic!("{} got unexpected event!: {:?}", std::stringify!($node), e);
67+
}
68+
}
69+
}};
70+
}
71+
72+
pub(crate) use expect_channel_ready_event;
73+
74+
pub(crate) fn random_storage_path() -> PathBuf {
75+
let mut temp_path = std::env::temp_dir();
76+
let mut rng = thread_rng();
77+
let rand_dir: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect();
78+
temp_path.push(rand_dir);
79+
temp_path
80+
}
81+
82+
pub(crate) fn random_port() -> u16 {
83+
let mut rng = thread_rng();
84+
rng.gen_range(5000..65535)
85+
}
86+
87+
pub(crate) fn random_listening_addresses() -> Vec<SocketAddress> {
88+
let num_addresses = 2;
89+
let mut listening_addresses = Vec::with_capacity(num_addresses);
90+
91+
for _ in 0..num_addresses {
92+
let rand_port = random_port();
93+
let address: SocketAddress = format!("127.0.0.1:{}", rand_port).parse().unwrap();
94+
listening_addresses.push(address);
95+
}
96+
97+
listening_addresses
98+
}
99+
100+
pub(crate) fn random_config() -> Config {
101+
let mut config = Config::default();
102+
103+
config.network = Network::Regtest;
104+
println!("Setting network: {}", config.network);
105+
106+
let rand_dir = random_storage_path();
107+
println!("Setting random LDK storage dir: {}", rand_dir.display());
108+
config.storage_dir_path = rand_dir.to_str().unwrap().to_owned();
109+
110+
let rand_listening_addresses = random_listening_addresses();
111+
println!("Setting random LDK listening addresses: {:?}", rand_listening_addresses);
112+
config.listening_addresses = Some(rand_listening_addresses);
113+
114+
config.log_level = LogLevel::Gossip;
115+
116+
config
117+
}
118+
119+
pub(crate) fn generate_blocks_and_wait(
120+
bitcoind: &BitcoindClient, electrs: &ElectrumClient, num: usize,
121+
) {
122+
let _ = bitcoind.create_wallet("ldk_node_test", None, None, None, None);
123+
let _ = bitcoind.load_wallet("ldk_node_test");
124+
print!("Generating {} blocks...", num);
125+
let cur_height = bitcoind.get_block_count().expect("failed to get current block height");
126+
let address = bitcoind
127+
.get_new_address(Some("test"), Some(AddressType::Legacy))
128+
.expect("failed to get new address");
129+
// TODO: expect this Result once the WouldBlock issue is resolved upstream.
130+
let _block_hashes_res = bitcoind.generate_to_address(num as u64, &address);
131+
wait_for_block(electrs, cur_height as usize + num);
132+
print!(" Done!");
133+
println!("\n");
134+
}
135+
136+
pub(crate) fn wait_for_block(electrs: &ElectrumClient, min_height: usize) {
137+
let mut header = match electrs.block_headers_subscribe() {
138+
Ok(header) => header,
139+
Err(_) => {
140+
// While subscribing should succeed the first time around, we ran into some cases where
141+
// it didn't. Since we can't proceed without subscribing, we try again after a delay
142+
// and panic if it still fails.
143+
std::thread::sleep(Duration::from_secs(1));
144+
electrs.block_headers_subscribe().expect("failed to subscribe to block headers")
145+
}
146+
};
147+
loop {
148+
if header.height >= min_height {
149+
break;
150+
}
151+
header = exponential_backoff_poll(|| {
152+
electrs.ping().expect("failed to ping electrs");
153+
electrs.block_headers_pop().expect("failed to pop block header")
154+
});
155+
}
156+
}
157+
158+
pub(crate) fn wait_for_tx(electrs: &ElectrumClient, txid: Txid) {
159+
let mut tx_res = electrs.transaction_get(&txid);
160+
loop {
161+
if tx_res.is_ok() {
162+
break;
163+
}
164+
tx_res = exponential_backoff_poll(|| {
165+
electrs.ping().unwrap();
166+
Some(electrs.transaction_get(&txid))
167+
});
168+
}
169+
}
170+
171+
pub(crate) fn wait_for_outpoint_spend(electrs: &ElectrumClient, outpoint: OutPoint) {
172+
let tx = electrs.transaction_get(&outpoint.txid).unwrap();
173+
let txout_script = tx.output.get(outpoint.vout as usize).unwrap().clone().script_pubkey;
174+
let mut is_spent = !electrs.script_get_history(&txout_script).unwrap().is_empty();
175+
loop {
176+
if is_spent {
177+
break;
178+
}
179+
180+
is_spent = exponential_backoff_poll(|| {
181+
electrs.ping().unwrap();
182+
Some(!electrs.script_get_history(&txout_script).unwrap().is_empty())
183+
});
184+
}
185+
}
186+
187+
pub(crate) fn exponential_backoff_poll<T, F>(mut poll: F) -> T
188+
where
189+
F: FnMut() -> Option<T>,
190+
{
191+
let mut delay = Duration::from_millis(64);
192+
let mut tries = 0;
193+
loop {
194+
match poll() {
195+
Some(data) => break data,
196+
None if delay.as_millis() < 512 => {
197+
delay = delay.mul_f32(2.0);
198+
}
199+
200+
None => {}
201+
}
202+
assert!(tries < 20, "Reached max tries.");
203+
tries += 1;
204+
std::thread::sleep(delay);
205+
}
206+
}
207+
208+
pub(crate) fn premine_and_distribute_funds(
209+
bitcoind: &BitcoindClient, electrs: &ElectrumClient, addrs: Vec<Address>, amount: Amount,
210+
) {
211+
let _ = bitcoind.create_wallet("ldk_node_test", None, None, None, None);
212+
let _ = bitcoind.load_wallet("ldk_node_test");
213+
generate_blocks_and_wait(bitcoind, electrs, 101);
214+
215+
for addr in addrs {
216+
let txid =
217+
bitcoind.send_to_address(&addr, amount, None, None, None, None, None, None).unwrap();
218+
wait_for_tx(electrs, txid);
219+
}
220+
221+
generate_blocks_and_wait(bitcoind, electrs, 1);
222+
}

tests/integration_tests_cln.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#![cfg(cln_test)]
2+
3+
mod common;
4+
5+
use ldk_node::bitcoin::secp256k1::PublicKey;
6+
use ldk_node::bitcoin::Amount;
7+
use ldk_node::lightning::ln::msgs::SocketAddress;
8+
use ldk_node::{Builder, Event};
9+
10+
use clightningrpc::lightningrpc::LightningRPC;
11+
use clightningrpc::responses::NetworkAddress;
12+
13+
use bitcoincore_rpc::Auth;
14+
use bitcoincore_rpc::Client as BitcoindClient;
15+
16+
use electrum_client::Client as ElectrumClient;
17+
use lightning_invoice::Bolt11Invoice;
18+
19+
use rand::distributions::Alphanumeric;
20+
use rand::{thread_rng, Rng};
21+
22+
use std::default::Default;
23+
use std::str::FromStr;
24+
25+
#[test]
26+
fn test_cln() {
27+
// Setup bitcoind / electrs clients
28+
let bitcoind_client = BitcoindClient::new(
29+
"127.0.0.1:18443",
30+
Auth::UserPass("user".to_string(), "pass".to_string()),
31+
)
32+
.unwrap();
33+
let electrs_client = ElectrumClient::new("tcp://127.0.0.1:50001").unwrap();
34+
35+
// Give electrs a kick.
36+
common::generate_blocks_and_wait(&bitcoind_client, &electrs_client, 1);
37+
38+
// Setup LDK Node
39+
let config = common::random_config();
40+
let mut builder = Builder::from_config(config);
41+
builder.set_esplora_server("http://127.0.0.1:3002".to_string());
42+
43+
let node = builder.build().unwrap();
44+
node.start().unwrap();
45+
46+
// Premine some funds and distribute
47+
let address = node.new_onchain_address().unwrap();
48+
let premine_amount = Amount::from_sat(5_000_000);
49+
common::premine_and_distribute_funds(
50+
&bitcoind_client,
51+
&electrs_client,
52+
vec![address],
53+
premine_amount,
54+
);
55+
56+
// Setup CLN
57+
let sock = "/tmp/lightning-rpc";
58+
let cln_client = LightningRPC::new(&sock);
59+
let cln_info = cln_client.getinfo().unwrap();
60+
let cln_node_id = PublicKey::from_str(&cln_info.id).unwrap();
61+
let cln_address: SocketAddress = match cln_info.binding.first().unwrap() {
62+
NetworkAddress::Ipv4 { address, port } => {
63+
std::net::SocketAddrV4::new(*address, *port).into()
64+
}
65+
NetworkAddress::Ipv6 { address, port } => {
66+
std::net::SocketAddrV6::new(*address, *port, 0, 0).into()
67+
}
68+
_ => {
69+
panic!()
70+
}
71+
};
72+
73+
node.sync_wallets().unwrap();
74+
75+
// Open the channel
76+
let funding_amount_sat = 1_000_000;
77+
78+
node.connect_open_channel(
79+
cln_node_id,
80+
cln_address,
81+
funding_amount_sat,
82+
Some(500_000_000),
83+
None,
84+
false,
85+
)
86+
.unwrap();
87+
88+
let funding_txo = common::expect_channel_pending_event!(node, cln_node_id);
89+
common::wait_for_tx(&electrs_client, funding_txo.txid);
90+
common::generate_blocks_and_wait(&bitcoind_client, &electrs_client, 6);
91+
let channel_id = common::expect_channel_ready_event!(node, cln_node_id);
92+
93+
// Send a payment to CLN
94+
let mut rng = thread_rng();
95+
let rand_label: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect();
96+
let cln_invoice =
97+
cln_client.invoice(Some(2_500_000), &rand_label, &rand_label, None, None, None).unwrap();
98+
let parsed_invoice = Bolt11Invoice::from_str(&cln_invoice.bolt11).unwrap();
99+
100+
node.send_payment(&parsed_invoice).unwrap();
101+
common::expect_event!(node, PaymentSuccessful);
102+
let cln_listed_invoices =
103+
cln_client.listinvoices(Some(&rand_label), None, None, None).unwrap().invoices;
104+
assert_eq!(cln_listed_invoices.len(), 1);
105+
assert_eq!(cln_listed_invoices.first().unwrap().status, "paid");
106+
107+
// Send a payment to LDK
108+
let rand_label: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect();
109+
let ldk_invoice = node.receive_payment(2_500_000, &rand_label, 3600).unwrap();
110+
cln_client.pay(&ldk_invoice.to_string(), Default::default()).unwrap();
111+
common::expect_event!(node, PaymentReceived);
112+
113+
node.close_channel(&channel_id, cln_node_id).unwrap();
114+
common::expect_event!(node, ChannelClosed);
115+
node.stop().unwrap();
116+
}

0 commit comments

Comments
 (0)