Skip to content

Commit 2fd097f

Browse files
committed
Add CLN integration tests
1 parent b231aa4 commit 2fd097f

File tree

3 files changed

+290
-0
lines changed

3 files changed

+290
-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.23.0", features = ["build"], optional = true }
8690

tests/common.rs

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
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+
print!("Generating {} blocks...", num);
105+
let cur_height = bitcoind.get_block_count().expect("failed to get current block height");
106+
let address = bitcoind
107+
.get_new_address(Some("test"), Some(AddressType::Legacy))
108+
.expect("failed to get new address");
109+
// TODO: expect this Result once the WouldBlock issue is resolved upstream.
110+
let _block_hashes_res = bitcoind.generate_to_address(num as u64, &address);
111+
wait_for_block(electrs, cur_height as usize + num);
112+
print!(" Done!");
113+
println!("\n");
114+
}
115+
116+
pub(crate) fn wait_for_block(electrs: &ElectrumClient, min_height: usize) {
117+
let mut header = match electrs.block_headers_subscribe() {
118+
Ok(header) => header,
119+
Err(_) => {
120+
// While subscribing should succeed the first time around, we ran into some cases where
121+
// it didn't. Since we can't proceed without subscribing, we try again after a delay
122+
// and panic if it still fails.
123+
std::thread::sleep(Duration::from_secs(1));
124+
electrs.block_headers_subscribe().expect("failed to subscribe to block headers")
125+
}
126+
};
127+
loop {
128+
if header.height >= min_height {
129+
break;
130+
}
131+
header = exponential_backoff_poll(|| {
132+
electrs.ping().expect("failed to ping electrs");
133+
electrs.block_headers_pop().expect("failed to pop block header")
134+
});
135+
}
136+
}
137+
138+
pub(crate) fn wait_for_tx(electrs: &ElectrumClient, txid: Txid) {
139+
let mut tx_res = electrs.transaction_get(&txid);
140+
loop {
141+
if tx_res.is_ok() {
142+
break;
143+
}
144+
tx_res = exponential_backoff_poll(|| {
145+
electrs.ping().unwrap();
146+
Some(electrs.transaction_get(&txid))
147+
});
148+
}
149+
}
150+
151+
pub(crate) fn wait_for_outpoint_spend(electrs: &ElectrumClient, outpoint: OutPoint) {
152+
let tx = electrs.transaction_get(&outpoint.txid).unwrap();
153+
let txout_script = tx.output.get(outpoint.vout as usize).unwrap().clone().script_pubkey;
154+
let mut is_spent = !electrs.script_get_history(&txout_script).unwrap().is_empty();
155+
loop {
156+
if is_spent {
157+
break;
158+
}
159+
160+
is_spent = exponential_backoff_poll(|| {
161+
electrs.ping().unwrap();
162+
Some(!electrs.script_get_history(&txout_script).unwrap().is_empty())
163+
});
164+
}
165+
}
166+
167+
pub(crate) fn exponential_backoff_poll<T, F>(mut poll: F) -> T
168+
where
169+
F: FnMut() -> Option<T>,
170+
{
171+
let mut delay = Duration::from_millis(64);
172+
let mut tries = 0;
173+
loop {
174+
match poll() {
175+
Some(data) => break data,
176+
None if delay.as_millis() < 512 => {
177+
delay = delay.mul_f32(2.0);
178+
}
179+
180+
None => {}
181+
}
182+
assert!(tries < 20, "Reached max tries.");
183+
tries += 1;
184+
std::thread::sleep(delay);
185+
}
186+
}
187+
188+
pub(crate) fn premine_and_distribute_funds(
189+
bitcoind: &BitcoindClient, electrs: &ElectrumClient, addrs: Vec<Address>, amount: Amount,
190+
) {
191+
let _ = bitcoind.create_wallet("ldk_node_test", None, None, None, None);
192+
generate_blocks_and_wait(bitcoind, electrs, 101);
193+
194+
for addr in addrs {
195+
let txid =
196+
bitcoind.send_to_address(&addr, amount, None, None, None, None, None, None).unwrap();
197+
wait_for_tx(electrs, txid);
198+
}
199+
200+
generate_blocks_and_wait(bitcoind, electrs, 1);
201+
}

tests/integration_tests_cln.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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::bitcoincore_rpc_json::AddressType;
14+
use bitcoincore_rpc::Client as BitcoindClient;
15+
use bitcoincore_rpc::{Auth, RpcApi};
16+
17+
use electrum_client::Client as ElectrumClient;
18+
19+
use std::str::FromStr;
20+
21+
#[test]
22+
fn test_cln() {
23+
// Setup bitcoind / electrs clients
24+
let bitcoind_client = BitcoindClient::new(
25+
"127.0.0.1:18443",
26+
Auth::UserPass("user".to_string(), "pass".to_string()),
27+
)
28+
.unwrap();
29+
let electrs_client = ElectrumClient::new("tcp://127.0.0.1:50001").unwrap();
30+
31+
// Setup LDK Node
32+
let config = common::random_config();
33+
let mut builder = Builder::from_config(config);
34+
builder.set_esplora_server("http://127.0.0.1:3002".to_string());
35+
36+
let node = builder.build().unwrap();
37+
node.start().unwrap();
38+
39+
// Premine some funds and distribute
40+
let address = node.new_onchain_address().unwrap();
41+
let premine_amount = Amount::from_sat(100_000);
42+
common::premine_and_distribute_funds(
43+
&bitcoind_client,
44+
&electrs_client,
45+
vec![address],
46+
premine_amount,
47+
);
48+
49+
// Setup CLN
50+
let sock = "/tmp/lightning-rpc";
51+
let cln_client = LightningRPC::new(&sock);
52+
let cln_info = cln_client.getinfo().unwrap();
53+
let cln_node_id = PublicKey::from_str(&cln_info.id).unwrap();
54+
let cln_address: SocketAddress = match cln_info.binding.first().unwrap() {
55+
NetworkAddress::Ipv4 { address, port } => {
56+
std::net::SocketAddrV4::new(*address, *port).into()
57+
}
58+
NetworkAddress::Ipv6 { address, port } => {
59+
std::net::SocketAddrV6::new(*address, *port, 0, 0).into()
60+
}
61+
_ => {
62+
panic!()
63+
}
64+
};
65+
66+
node.sync_wallets().unwrap();
67+
println!("FUNDS: {:?}", node.spendable_onchain_balance_sats());
68+
println!("FUNDS: {:?}", node.total_onchain_balance_sats());
69+
70+
std::thread::sleep(std::time::Duration::from_secs(10));
71+
let funding_amount_sat = 80_000;
72+
73+
node.connect_open_channel(cln_node_id, cln_address, funding_amount_sat, None, None, false)
74+
.unwrap();
75+
76+
let funding_txo = common::expect_channel_pending_event!(node, cln_node_id);
77+
common::wait_for_tx(&electrs_client, funding_txo.txid);
78+
common::generate_blocks_and_wait(&bitcoind_client, &electrs_client, 6);
79+
common::expect_event!(node, ChannelReady);
80+
81+
//println!("PEERS: {:?}", node.list_peers());
82+
//println!("LDK Channels: {:?}", node.list_channels());
83+
//println!("CLN Channels: {:?}", cln_client.listchannels(None, None, None));
84+
node.stop().unwrap();
85+
}

0 commit comments

Comments
 (0)