Skip to content

Commit 0462c46

Browse files
committed
Add CLN integration tests
1 parent 20d9d76 commit 0462c46

File tree

3 files changed

+314
-0
lines changed

3 files changed

+314
-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: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
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+
pub(crate) fn random_storage_path() -> PathBuf {
57+
let mut temp_path = std::env::temp_dir();
58+
let mut rng = thread_rng();
59+
let rand_dir: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect();
60+
temp_path.push(rand_dir);
61+
temp_path
62+
}
63+
64+
pub(crate) fn random_port() -> u16 {
65+
let mut rng = thread_rng();
66+
rng.gen_range(5000..65535)
67+
}
68+
69+
pub(crate) fn random_listening_addresses() -> Vec<SocketAddress> {
70+
let num_addresses = 2;
71+
let mut listening_addresses = Vec::with_capacity(num_addresses);
72+
73+
for _ in 0..num_addresses {
74+
let rand_port = random_port();
75+
let address: SocketAddress = format!("127.0.0.1:{}", rand_port).parse().unwrap();
76+
listening_addresses.push(address);
77+
}
78+
79+
listening_addresses
80+
}
81+
82+
pub(crate) fn random_config() -> Config {
83+
let mut config = Config::default();
84+
85+
config.network = Network::Regtest;
86+
println!("Setting network: {}", config.network);
87+
88+
let rand_dir = random_storage_path();
89+
println!("Setting random LDK storage dir: {}", rand_dir.display());
90+
config.storage_dir_path = rand_dir.to_str().unwrap().to_owned();
91+
92+
let rand_listening_addresses = random_listening_addresses();
93+
println!("Setting random LDK listening addresses: {:?}", rand_listening_addresses);
94+
config.listening_addresses = Some(rand_listening_addresses);
95+
96+
config.log_level = LogLevel::Gossip;
97+
98+
config
99+
}
100+
101+
pub(crate) fn generate_blocks_and_wait(
102+
bitcoind: &BitcoindClient, electrs: &ElectrumClient, num: usize,
103+
) {
104+
let _ = bitcoind.create_wallet("ldk_node_test", None, None, None, None);
105+
let _ = bitcoind.load_wallet("ldk_node_test");
106+
print!("Generating {} blocks...", num);
107+
let cur_height = bitcoind.get_block_count().expect("failed to get current block height");
108+
let address = bitcoind
109+
.get_new_address(Some("test"), Some(AddressType::Legacy))
110+
.expect("failed to get new address");
111+
// TODO: expect this Result once the WouldBlock issue is resolved upstream.
112+
let _block_hashes_res = bitcoind.generate_to_address(num as u64, &address);
113+
wait_for_block(electrs, cur_height as usize + num);
114+
print!(" Done!");
115+
println!("\n");
116+
}
117+
118+
pub(crate) fn wait_for_block(electrs: &ElectrumClient, min_height: usize) {
119+
let mut header = match electrs.block_headers_subscribe() {
120+
Ok(header) => header,
121+
Err(_) => {
122+
// While subscribing should succeed the first time around, we ran into some cases where
123+
// it didn't. Since we can't proceed without subscribing, we try again after a delay
124+
// and panic if it still fails.
125+
std::thread::sleep(Duration::from_secs(1));
126+
electrs.block_headers_subscribe().expect("failed to subscribe to block headers")
127+
}
128+
};
129+
loop {
130+
if header.height >= min_height {
131+
break;
132+
}
133+
header = exponential_backoff_poll(|| {
134+
electrs.ping().expect("failed to ping electrs");
135+
electrs.block_headers_pop().expect("failed to pop block header")
136+
});
137+
}
138+
}
139+
140+
pub(crate) fn wait_for_tx(electrs: &ElectrumClient, txid: Txid) {
141+
let mut tx_res = electrs.transaction_get(&txid);
142+
loop {
143+
if tx_res.is_ok() {
144+
break;
145+
}
146+
tx_res = exponential_backoff_poll(|| {
147+
electrs.ping().unwrap();
148+
Some(electrs.transaction_get(&txid))
149+
});
150+
}
151+
}
152+
153+
pub(crate) fn wait_for_outpoint_spend(electrs: &ElectrumClient, outpoint: OutPoint) {
154+
let tx = electrs.transaction_get(&outpoint.txid).unwrap();
155+
let txout_script = tx.output.get(outpoint.vout as usize).unwrap().clone().script_pubkey;
156+
let mut is_spent = !electrs.script_get_history(&txout_script).unwrap().is_empty();
157+
loop {
158+
if is_spent {
159+
break;
160+
}
161+
162+
is_spent = exponential_backoff_poll(|| {
163+
electrs.ping().unwrap();
164+
Some(!electrs.script_get_history(&txout_script).unwrap().is_empty())
165+
});
166+
}
167+
}
168+
169+
pub(crate) fn exponential_backoff_poll<T, F>(mut poll: F) -> T
170+
where
171+
F: FnMut() -> Option<T>,
172+
{
173+
let mut delay = Duration::from_millis(64);
174+
let mut tries = 0;
175+
loop {
176+
match poll() {
177+
Some(data) => break data,
178+
None if delay.as_millis() < 512 => {
179+
delay = delay.mul_f32(2.0);
180+
}
181+
182+
None => {}
183+
}
184+
assert!(tries < 20, "Reached max tries.");
185+
tries += 1;
186+
std::thread::sleep(delay);
187+
}
188+
}
189+
190+
pub(crate) fn premine_and_distribute_funds(
191+
bitcoind: &BitcoindClient, electrs: &ElectrumClient, addrs: Vec<Address>, amount: Amount,
192+
) {
193+
let _ = bitcoind.create_wallet("ldk_node_test", None, None, None, None);
194+
let _ = bitcoind.load_wallet("ldk_node_test");
195+
generate_blocks_and_wait(bitcoind, electrs, 101);
196+
197+
for addr in addrs {
198+
let txid =
199+
bitcoind.send_to_address(&addr, amount, None, None, None, None, None, None).unwrap();
200+
wait_for_tx(electrs, txid);
201+
}
202+
203+
generate_blocks_and_wait(bitcoind, electrs, 1);
204+
}

tests/integration_tests_cln.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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(cln_node_id, cln_address, funding_amount_sat, None, None, false)
79+
.unwrap();
80+
81+
let funding_txo = common::expect_channel_pending_event!(node, cln_node_id);
82+
common::wait_for_tx(&electrs_client, funding_txo.txid);
83+
common::generate_blocks_and_wait(&bitcoind_client, &electrs_client, 6);
84+
common::expect_event!(node, ChannelReady);
85+
86+
// Send a payment to CLN
87+
let mut rng = thread_rng();
88+
let rand_label: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect();
89+
let cln_invoice =
90+
cln_client.invoice(Some(3_000_000), &rand_label, &rand_label, None, None, None).unwrap();
91+
let parsed_invoice = Bolt11Invoice::from_str(&cln_invoice.bolt11).unwrap();
92+
93+
node.send_payment(&parsed_invoice).unwrap();
94+
common::expect_event!(node, PaymentSuccessful);
95+
let cln_listed_invoices =
96+
cln_client.listinvoices(Some(&rand_label), None, None, None).unwrap().invoices;
97+
assert_eq!(cln_listed_invoices.len(), 1);
98+
assert_eq!(cln_listed_invoices.first().unwrap().status, "paid");
99+
100+
// Send a payment to LDK
101+
let rand_label: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect();
102+
let ldk_invoice = node.receive_payment(2_500_000, &rand_label, 3600).unwrap();
103+
cln_client.pay(&ldk_invoice.to_string(), Default::default()).unwrap();
104+
common::expect_event!(node, PaymentReceived);
105+
node.stop().unwrap();
106+
}

0 commit comments

Comments
 (0)