Skip to content

Commit

Permalink
Add CLN integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
tnull committed Dec 11, 2023
1 parent 20d9d76 commit 4d3515b
Show file tree
Hide file tree
Showing 3 changed files with 342 additions and 0 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ electrum-client = "0.12.0"
proptest = "1.0.0"
regex = "1.5.6"

[target.'cfg(cln_test)'.dev-dependencies]
clightningrpc = { version = "0.3.0-beta.8", default-features = false }
bitcoincore-rpc = { version = "0.16.0", default-features = false }

[build-dependencies]
uniffi = { version = "0.25.1", features = ["build"], optional = true }

Expand Down
222 changes: 222 additions & 0 deletions tests/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
#![cfg(cln_test)]

use ldk_node::{Config, LogLevel};

use lightning::ln::msgs::SocketAddress;

use bitcoin::{Address, Amount, Network, OutPoint, Txid};

use bitcoincore_rpc::bitcoincore_rpc_json::AddressType;
use bitcoincore_rpc::Client as BitcoindClient;
use bitcoincore_rpc::RpcApi;

use electrum_client::Client as ElectrumClient;
use electrum_client::ElectrumApi;

use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};

use std::path::PathBuf;
use std::time::Duration;

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

pub(crate) use expect_event;

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

pub(crate) use expect_channel_pending_event;

macro_rules! expect_channel_ready_event {
($node: expr, $counterparty_node_id: expr) => {{
match $node.wait_next_event() {
ref e @ Event::ChannelReady { channel_id, counterparty_node_id, .. } => {
println!("{} got event {:?}", $node.node_id(), e);
assert_eq!(counterparty_node_id, Some($counterparty_node_id));
$node.event_handled();
channel_id
}
ref e => {
panic!("{} got unexpected event!: {:?}", std::stringify!($node), e);
}
}
}};
}

pub(crate) use expect_channel_ready_event;

pub(crate) fn random_storage_path() -> PathBuf {
let mut temp_path = std::env::temp_dir();
let mut rng = thread_rng();
let rand_dir: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect();
temp_path.push(rand_dir);
temp_path
}

pub(crate) fn random_port() -> u16 {
let mut rng = thread_rng();
rng.gen_range(5000..65535)
}

pub(crate) fn random_listening_addresses() -> Vec<SocketAddress> {
let num_addresses = 2;
let mut listening_addresses = Vec::with_capacity(num_addresses);

for _ in 0..num_addresses {
let rand_port = random_port();
let address: SocketAddress = format!("127.0.0.1:{}", rand_port).parse().unwrap();
listening_addresses.push(address);
}

listening_addresses
}

pub(crate) fn random_config() -> Config {
let mut config = Config::default();

config.network = Network::Regtest;
println!("Setting network: {}", config.network);

let rand_dir = random_storage_path();
println!("Setting random LDK storage dir: {}", rand_dir.display());
config.storage_dir_path = rand_dir.to_str().unwrap().to_owned();

let rand_listening_addresses = random_listening_addresses();
println!("Setting random LDK listening addresses: {:?}", rand_listening_addresses);
config.listening_addresses = Some(rand_listening_addresses);

config.log_level = LogLevel::Gossip;

config
}

pub(crate) fn generate_blocks_and_wait(
bitcoind: &BitcoindClient, electrs: &ElectrumClient, num: usize,
) {
let _ = bitcoind.create_wallet("ldk_node_test", None, None, None, None);
let _ = bitcoind.load_wallet("ldk_node_test");
print!("Generating {} blocks...", num);
let cur_height = bitcoind.get_block_count().expect("failed to get current block height");
let address = bitcoind
.get_new_address(Some("test"), Some(AddressType::Legacy))
.expect("failed to get new address");
// TODO: expect this Result once the WouldBlock issue is resolved upstream.
let _block_hashes_res = bitcoind.generate_to_address(num as u64, &address);
wait_for_block(electrs, cur_height as usize + num);
print!(" Done!");
println!("\n");
}

pub(crate) fn wait_for_block(electrs: &ElectrumClient, min_height: usize) {
let mut header = match electrs.block_headers_subscribe() {
Ok(header) => header,
Err(_) => {
// While subscribing should succeed the first time around, we ran into some cases where
// it didn't. Since we can't proceed without subscribing, we try again after a delay
// and panic if it still fails.
std::thread::sleep(Duration::from_secs(1));
electrs.block_headers_subscribe().expect("failed to subscribe to block headers")
}
};
loop {
if header.height >= min_height {
break;
}
header = exponential_backoff_poll(|| {
electrs.ping().expect("failed to ping electrs");
electrs.block_headers_pop().expect("failed to pop block header")
});
}
}

pub(crate) fn wait_for_tx(electrs: &ElectrumClient, txid: Txid) {
let mut tx_res = electrs.transaction_get(&txid);
loop {
if tx_res.is_ok() {
break;
}
tx_res = exponential_backoff_poll(|| {
electrs.ping().unwrap();
Some(electrs.transaction_get(&txid))
});
}
}

pub(crate) fn wait_for_outpoint_spend(electrs: &ElectrumClient, outpoint: OutPoint) {
let tx = electrs.transaction_get(&outpoint.txid).unwrap();
let txout_script = tx.output.get(outpoint.vout as usize).unwrap().clone().script_pubkey;
let mut is_spent = !electrs.script_get_history(&txout_script).unwrap().is_empty();
loop {
if is_spent {
break;
}

is_spent = exponential_backoff_poll(|| {
electrs.ping().unwrap();
Some(!electrs.script_get_history(&txout_script).unwrap().is_empty())
});
}
}

pub(crate) fn exponential_backoff_poll<T, F>(mut poll: F) -> T
where
F: FnMut() -> Option<T>,
{
let mut delay = Duration::from_millis(64);
let mut tries = 0;
loop {
match poll() {
Some(data) => break data,
None if delay.as_millis() < 512 => {
delay = delay.mul_f32(2.0);
}

None => {}
}
assert!(tries < 20, "Reached max tries.");
tries += 1;
std::thread::sleep(delay);
}
}

pub(crate) fn premine_and_distribute_funds(
bitcoind: &BitcoindClient, electrs: &ElectrumClient, addrs: Vec<Address>, amount: Amount,
) {
let _ = bitcoind.create_wallet("ldk_node_test", None, None, None, None);
let _ = bitcoind.load_wallet("ldk_node_test");
generate_blocks_and_wait(bitcoind, electrs, 101);

for addr in addrs {
let txid =
bitcoind.send_to_address(&addr, amount, None, None, None, None, None, None).unwrap();
wait_for_tx(electrs, txid);
}

generate_blocks_and_wait(bitcoind, electrs, 1);
}
116 changes: 116 additions & 0 deletions tests/integration_tests_cln.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#![cfg(cln_test)]

mod common;

use ldk_node::bitcoin::secp256k1::PublicKey;
use ldk_node::bitcoin::Amount;
use ldk_node::lightning::ln::msgs::SocketAddress;
use ldk_node::{Builder, Event};

use clightningrpc::lightningrpc::LightningRPC;
use clightningrpc::responses::NetworkAddress;

use bitcoincore_rpc::Auth;
use bitcoincore_rpc::Client as BitcoindClient;

use electrum_client::Client as ElectrumClient;
use lightning_invoice::Bolt11Invoice;

use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};

use std::default::Default;
use std::str::FromStr;

#[test]
fn test_cln() {
// Setup bitcoind / electrs clients
let bitcoind_client = BitcoindClient::new(
"127.0.0.1:18443",
Auth::UserPass("user".to_string(), "pass".to_string()),
)
.unwrap();
let electrs_client = ElectrumClient::new("tcp://127.0.0.1:50001").unwrap();

// Give electrs a kick.
common::generate_blocks_and_wait(&bitcoind_client, &electrs_client, 1);

// Setup LDK Node
let config = common::random_config();
let mut builder = Builder::from_config(config);
builder.set_esplora_server("http://127.0.0.1:3002".to_string());

let node = builder.build().unwrap();
node.start().unwrap();

// Premine some funds and distribute
let address = node.new_onchain_address().unwrap();
let premine_amount = Amount::from_sat(5_000_000);
common::premine_and_distribute_funds(
&bitcoind_client,
&electrs_client,
vec![address],
premine_amount,
);

// Setup CLN
let sock = "/tmp/lightning-rpc";
let cln_client = LightningRPC::new(&sock);
let cln_info = cln_client.getinfo().unwrap();
let cln_node_id = PublicKey::from_str(&cln_info.id).unwrap();
let cln_address: SocketAddress = match cln_info.binding.first().unwrap() {
NetworkAddress::Ipv4 { address, port } => {
std::net::SocketAddrV4::new(*address, *port).into()
}
NetworkAddress::Ipv6 { address, port } => {
std::net::SocketAddrV6::new(*address, *port, 0, 0).into()
}
_ => {
panic!()
}
};

node.sync_wallets().unwrap();

// Open the channel
let funding_amount_sat = 1_000_000;

node.connect_open_channel(
cln_node_id,
cln_address,
funding_amount_sat,
Some(500_000_000),
None,
false,
)
.unwrap();

let funding_txo = common::expect_channel_pending_event!(node, cln_node_id);
common::wait_for_tx(&electrs_client, funding_txo.txid);
common::generate_blocks_and_wait(&bitcoind_client, &electrs_client, 6);
let channel_id = common::expect_channel_ready_event!(node, cln_node_id);

// Send a payment to CLN
let mut rng = thread_rng();
let rand_label: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect();
let cln_invoice =
cln_client.invoice(Some(2_500_000), &rand_label, &rand_label, None, None, None).unwrap();
let parsed_invoice = Bolt11Invoice::from_str(&cln_invoice.bolt11).unwrap();

node.send_payment(&parsed_invoice).unwrap();
common::expect_event!(node, PaymentSuccessful);
let cln_listed_invoices =
cln_client.listinvoices(Some(&rand_label), None, None, None).unwrap().invoices;
assert_eq!(cln_listed_invoices.len(), 1);
assert_eq!(cln_listed_invoices.first().unwrap().status, "paid");

// Send a payment to LDK
let rand_label: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect();
let ldk_invoice = node.receive_payment(2_500_000, &rand_label, 3600).unwrap();
cln_client.pay(&ldk_invoice.to_string(), Default::default()).unwrap();
common::expect_event!(node, PaymentReceived);

node.close_channel(&channel_id, cln_node_id).unwrap();
common::expect_event!(node, ChannelClosed);
node.stop().unwrap();
}

0 comments on commit 4d3515b

Please sign in to comment.