Skip to content

Commit 303e0ee

Browse files
committed
Add test setup and first end-to-end test
1 parent f916490 commit 303e0ee

File tree

5 files changed

+224
-0
lines changed

5 files changed

+224
-0
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ futures = "0.3"
4848
serde_json = { version = "1.0" }
4949
tokio = { version = "1", features = [ "full" ] }
5050

51+
[dev-dependencies]
52+
electrsd = { version = "0.22.0", features = ["legacy", "esplora_a33e97e1", "bitcoind_23_0"] }
53+
electrum-client = "0.12.0"
54+
once_cell = "1.16.0"
5155

5256
[profile.release]
5357
panic = "abort"

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ mod hex_utils;
3030
mod io_utils;
3131
mod logger;
3232
mod peer_store;
33+
#[cfg(test)]
34+
mod tests;
3335
mod wallet;
3436

3537
pub use error::Error;

src/tests/functional_tests.rs

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
use crate::tests::test_utils::expect_event;
2+
use crate::{Builder, Config, Event};
3+
4+
use bitcoin::{Address, Amount};
5+
use bitcoind::bitcoincore_rpc::RpcApi;
6+
use electrsd::bitcoind::bitcoincore_rpc::bitcoincore_rpc_json::AddressType;
7+
use electrsd::{bitcoind, bitcoind::BitcoinD, ElectrsD};
8+
use electrum_client::ElectrumApi;
9+
10+
use once_cell::sync::OnceCell;
11+
use rand::distributions::Alphanumeric;
12+
use rand::{thread_rng, Rng};
13+
14+
use std::env;
15+
use std::sync::Mutex;
16+
use std::time::Duration;
17+
18+
static BITCOIND: OnceCell<BitcoinD> = OnceCell::new();
19+
static ELECTRSD: OnceCell<ElectrsD> = OnceCell::new();
20+
static PREMINE: OnceCell<()> = OnceCell::new();
21+
static MINER_LOCK: Mutex<()> = Mutex::new(());
22+
23+
fn get_bitcoind() -> &'static BitcoinD {
24+
BITCOIND.get_or_init(|| {
25+
let bitcoind_exe =
26+
env::var("BITCOIND_EXE").ok().or_else(|| bitcoind::downloaded_exe_path().ok()).expect(
27+
"you need to provide an env var BITCOIND_EXE or specify a bitcoind version feature",
28+
);
29+
let mut conf = bitcoind::Conf::default();
30+
conf.network = "regtest";
31+
BitcoinD::with_conf(bitcoind_exe, &conf).unwrap()
32+
})
33+
}
34+
35+
fn get_electrsd() -> &'static ElectrsD {
36+
ELECTRSD.get_or_init(|| {
37+
let bitcoind = get_bitcoind();
38+
let electrs_exe =
39+
env::var("ELECTRS_EXE").ok().or_else(electrsd::downloaded_exe_path).expect(
40+
"you need to provide env var ELECTRS_EXE or specify an electrsd version feature",
41+
);
42+
let mut conf = electrsd::Conf::default();
43+
conf.http_enabled = true;
44+
conf.network = "regtest";
45+
ElectrsD::with_conf(electrs_exe, &bitcoind, &conf).unwrap()
46+
})
47+
}
48+
49+
fn generate_blocks_and_wait(num: usize) {
50+
let _miner = MINER_LOCK.lock().unwrap();
51+
let cur_height = get_bitcoind().client.get_block_count().unwrap();
52+
let address =
53+
get_bitcoind().client.get_new_address(Some("test"), Some(AddressType::Legacy)).unwrap();
54+
let _block_hashes = get_bitcoind().client.generate_to_address(num as u64, &address).unwrap();
55+
wait_for_block(cur_height as usize + num);
56+
}
57+
58+
fn wait_for_block(min_height: usize) {
59+
let mut header = get_electrsd().client.block_headers_subscribe().unwrap();
60+
loop {
61+
if header.height >= min_height {
62+
break;
63+
}
64+
header = exponential_backoff_poll(|| {
65+
get_electrsd().trigger().unwrap();
66+
get_electrsd().client.ping().unwrap();
67+
get_electrsd().client.block_headers_pop().unwrap()
68+
});
69+
}
70+
}
71+
72+
fn exponential_backoff_poll<T, F>(mut poll: F) -> T
73+
where
74+
F: FnMut() -> Option<T>,
75+
{
76+
let mut delay = Duration::from_millis(64);
77+
loop {
78+
match poll() {
79+
Some(data) => break data,
80+
None if delay.as_millis() < 512 => delay = delay.mul_f32(2.0),
81+
None => {}
82+
}
83+
84+
std::thread::sleep(delay);
85+
}
86+
}
87+
88+
fn premine_and_distribute_funds(addrs: Vec<Address>, amount: Amount) {
89+
PREMINE.get_or_init(|| {
90+
generate_blocks_and_wait(101);
91+
for addr in addrs {
92+
get_bitcoind()
93+
.client
94+
.send_to_address(&addr, amount, None, None, None, None, None, None)
95+
.unwrap();
96+
}
97+
generate_blocks_and_wait(1);
98+
});
99+
}
100+
101+
fn rand_config() -> Config {
102+
let mut config = Config::default();
103+
104+
let esplora_url = get_electrsd().esplora_url.as_ref().unwrap();
105+
106+
println!("Setting esplora server URL: {}", esplora_url);
107+
config.esplora_server_url = format!("http://{}", esplora_url);
108+
109+
let mut rng = thread_rng();
110+
let rand_dir: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect();
111+
let rand_path = format!("/tmp/{}", rand_dir);
112+
println!("Setting random LDK storage dir: {}", rand_dir);
113+
config.storage_dir_path = rand_path;
114+
115+
let rand_port: u16 = rng.gen_range(5000..8000);
116+
println!("Setting random LDK listening port: {}", rand_port);
117+
let listening_address = format!("127.0.0.1:{}", rand_port);
118+
config.listening_address = Some(listening_address);
119+
120+
config
121+
}
122+
123+
#[test]
124+
fn channel_full_cycle() {
125+
println!("== Node A ==");
126+
let config_a = rand_config();
127+
let mut node_a = Builder::from_config(config_a).build();
128+
node_a.start().unwrap();
129+
let addr_a = node_a.new_funding_address().unwrap();
130+
131+
println!("\n== Node B ==");
132+
let config_b = rand_config();
133+
let mut node_b = Builder::from_config(config_b).build();
134+
node_b.start().unwrap();
135+
let addr_b = node_b.new_funding_address().unwrap();
136+
137+
premine_and_distribute_funds(vec![addr_a, addr_b], Amount::from_sat(100000));
138+
node_a.sync_wallets().unwrap();
139+
node_b.sync_wallets().unwrap();
140+
assert_eq!(node_a.on_chain_balance().unwrap().get_spendable(), 100000);
141+
assert_eq!(node_b.on_chain_balance().unwrap().get_spendable(), 100000);
142+
143+
println!("\nA -- connect_open_channel -> B");
144+
let node_b_addr =
145+
format!("{}@{}", node_b.node_id().unwrap(), node_b.listening_address().unwrap());
146+
node_a.connect_open_channel(&node_b_addr, 50000, true).unwrap();
147+
148+
// Wait a sec so the funding tx can 'propagate' via EsploraD to BitcoinD.
149+
std::thread::sleep(Duration::from_secs(1));
150+
151+
println!("\n .. generating blocks, syncing wallets .. ");
152+
generate_blocks_and_wait(6);
153+
node_a.sync_wallets().unwrap();
154+
node_b.sync_wallets().unwrap();
155+
156+
let node_a_balance = node_a.on_chain_balance().unwrap();
157+
assert!(node_a_balance.get_spendable() < 50000);
158+
assert!(node_a_balance.get_spendable() > 40000);
159+
assert_eq!(node_b.on_chain_balance().unwrap().get_spendable(), 100000);
160+
161+
expect_event!(node_a, ChannelReady);
162+
163+
let channel_id = match node_b.next_event() {
164+
ref e @ Event::ChannelReady { channel_id, .. } => {
165+
println!("{} got event {:?}", std::stringify!(node_b), e);
166+
node_b.event_handled();
167+
channel_id
168+
}
169+
ref e => {
170+
panic!("{} got unexpected event!: {:?}", std::stringify!(node_b), e);
171+
}
172+
};
173+
174+
println!("\nB receive_payment");
175+
let invoice = node_b.receive_payment(Some(1000), &"asdf", 9217).unwrap();
176+
177+
println!("\nA send_payment");
178+
node_a.send_payment(invoice).unwrap();
179+
180+
expect_event!(node_a, PaymentSuccessful);
181+
expect_event!(node_b, PaymentReceived);
182+
183+
node_b.close_channel(&channel_id, &node_a.node_id().unwrap()).unwrap();
184+
expect_event!(node_a, ChannelClosed);
185+
expect_event!(node_b, ChannelClosed);
186+
187+
// Wait a sec so the shutdown tx can 'propagate' via EsploraD to BitcoinD.
188+
std::thread::sleep(Duration::from_secs(1));
189+
190+
generate_blocks_and_wait(1);
191+
node_a.sync_wallets().unwrap();
192+
193+
assert!(node_a.on_chain_balance().unwrap().get_spendable() > 90000);
194+
assert_eq!(node_b.on_chain_balance().unwrap().get_spendable(), 100000);
195+
196+
node_a.stop().unwrap();
197+
println!("\nA stopped");
198+
node_b.stop().unwrap();
199+
println!("\nB stopped");
200+
}

src/tests/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod functional_tests;
2+
pub mod test_utils;

src/test_utils.rs renamed to src/tests/test_utils.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,22 @@ use lightning::util::ser::Writeable;
33

44
use std::sync::atomic::{AtomicBool, Ordering};
55

6+
macro_rules! expect_event {
7+
($node: expr, $event_type: ident) => {{
8+
match $node.next_event() {
9+
ref e @ Event::$event_type { .. } => {
10+
println!("{} got event {:?}", std::stringify!($node), e);
11+
$node.event_handled();
12+
}
13+
ref e => {
14+
panic!("{} got unexpected event!: {:?}", std::stringify!($node), e);
15+
}
16+
}
17+
}};
18+
}
19+
20+
pub(crate) use expect_event;
21+
622
pub(crate) struct TestPersister {
723
pending_persist: AtomicBool,
824
}

0 commit comments

Comments
 (0)