Skip to content

Commit 76befd7

Browse files
committed
Implement first end-to-end test
1 parent 612eed7 commit 76befd7

File tree

4 files changed

+219
-2
lines changed

4 files changed

+219
-2
lines changed

Cargo.toml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,28 @@ lightning-background-processor = { git = "https://github.com/tnull/rust-lightnin
3030
lightning-rapid-gossip-sync = { git = "https://github.com/tnull/rust-lightning", branch="2022-11-add-transaction-sync-crate" }
3131
lightning-transaction-sync = { git = "https://github.com/tnull/rust-lightning", branch="2022-11-add-transaction-sync-crate", features = ["esplora-async"] }
3232

33-
bdk = { git = "https://github.com/tnull/bdk.git", branch="2022-10-use-esplora-merkleblock", features = ["use-esplora-async", "key-value-db"]}
33+
#lightning = { path = "../rust-lightning/lightning", features = ["max_level_trace", "std"] }
34+
#lightning-invoice = { path = "../rust-lightning/lightning-invoice" }
35+
#lightning-net-tokio = { path = "../rust-lightning/lightning-net-tokio" }
36+
#lightning-persister = { path = "../rust-lightning/lightning-persister" }
37+
#lightning-background-processor = { path = "../rust-lightning/lightning-background-processor" }
38+
#lightning-rapid-gossip-sync = { path = "../rust-lightning/lightning-rapid-gossip-sync" }
39+
#lightning-transaction-sync = { path = "../rust-lightning/lightning-transaction-sync", features = ["esplora-async"] }
40+
41+
bdk = { git = "https://github.com/tnull/bdk.git", branch="2022-10-use-esplora-merkleblock", default-features = false, features = ["async-interface", "use-esplora-async", "key-value-db"]}
3442
bitcoin = "0.29.2"
3543

3644
rand = "0.8.5"
3745
chrono = "0.4"
3846
futures = "0.3"
3947
serde_json = { version = "1.0" }
40-
tokio = { version = "1", features = [ "io-util", "macros", "rt", "rt-multi-thread", "sync", "net", "time" ] }
48+
tokio = { version = "1", features = [ "full" ] }
4149

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

4356
[profile.release]
4457
panic = "abort"

src/tests/functional_tests.rs

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
use crate::tests::test_utils::expect_event;
2+
use crate::{Builder, Config, Event};
3+
4+
use electrsd::{bitcoind, bitcoind::BitcoinD, ElectrsD};
5+
use lazy_static::lazy_static;
6+
use once_cell::sync::OnceCell;
7+
use rand::distributions::Alphanumeric;
8+
use rand::{thread_rng, Rng};
9+
use std::env;
10+
use std::sync::Mutex;
11+
use {
12+
bitcoin::{Address, Amount},
13+
electrsd::{
14+
bitcoind::bitcoincore_rpc::bitcoincore_rpc_json::AddressType,
15+
bitcoind::bitcoincore_rpc::RpcApi,
16+
},
17+
electrum_client::ElectrumApi,
18+
std::time::Duration,
19+
};
20+
21+
lazy_static! {
22+
static ref BITCOIND: BitcoinD = {
23+
let bitcoind_exe =
24+
env::var("BITCOIND_EXE").ok().or_else(|| bitcoind::downloaded_exe_path().ok()).expect(
25+
"you need to provide an env var BITCOIND_EXE or specify a bitcoind version feature",
26+
);
27+
let mut conf = bitcoind::Conf::default();
28+
conf.network = "regtest";
29+
BitcoinD::with_conf(bitcoind_exe, &conf).unwrap()
30+
};
31+
static ref ELECTRSD: ElectrsD = {
32+
let electrs_exe =
33+
env::var("ELECTRS_EXE").ok().or_else(electrsd::downloaded_exe_path).expect(
34+
"you need to provide env var ELECTRS_EXE or specify an electrsd version feature",
35+
);
36+
let mut conf = electrsd::Conf::default();
37+
conf.http_enabled = true;
38+
conf.network = "regtest";
39+
ElectrsD::with_conf(electrs_exe, &BITCOIND, &conf).unwrap()
40+
};
41+
static ref MINER: Mutex<()> = Mutex::new(());
42+
}
43+
44+
static PREMINE: OnceCell<()> = OnceCell::new();
45+
46+
fn generate_blocks_and_wait(num: usize) {
47+
let cur_height = BITCOIND.client.get_block_count().unwrap();
48+
generate_blocks(num);
49+
wait_for_block(cur_height as usize + num);
50+
}
51+
52+
fn generate_blocks(num: usize) {
53+
let address = BITCOIND.client.get_new_address(Some("test"), Some(AddressType::Legacy)).unwrap();
54+
let _block_hashes = BITCOIND.client.generate_to_address(num as u64, &address).unwrap();
55+
}
56+
57+
fn wait_for_block(min_height: usize) {
58+
let mut header = ELECTRSD.client.block_headers_subscribe().unwrap();
59+
loop {
60+
if header.height >= min_height {
61+
break;
62+
}
63+
header = exponential_backoff_poll(|| {
64+
ELECTRSD.trigger().unwrap();
65+
ELECTRSD.client.ping().unwrap();
66+
ELECTRSD.client.block_headers_pop().unwrap()
67+
});
68+
}
69+
}
70+
71+
fn exponential_backoff_poll<T, F>(mut poll: F) -> T
72+
where
73+
F: FnMut() -> Option<T>,
74+
{
75+
let mut delay = Duration::from_millis(64);
76+
loop {
77+
match poll() {
78+
Some(data) => break data,
79+
None if delay.as_millis() < 512 => delay = delay.mul_f32(2.0),
80+
None => {}
81+
}
82+
83+
std::thread::sleep(delay);
84+
}
85+
}
86+
87+
fn premine_and_distribute_funds(addrs: Vec<Address>, amount: Amount) {
88+
PREMINE.get_or_init(|| {
89+
let _miner = MINER.lock().unwrap();
90+
generate_blocks_and_wait(101);
91+
for addr in addrs {
92+
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 = 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 full_channel_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+
141+
println!("\nA -- connect_open_channel -> B");
142+
let node_b_addr =
143+
format!("{}@{}", node_b.node_id().unwrap(), node_b.listening_address().unwrap());
144+
node_a.connect_open_channel(&node_b_addr, 50000, true).unwrap();
145+
// TODO: why are those needed?
146+
node_a.sync_wallets().unwrap();
147+
node_b.sync_wallets().unwrap();
148+
149+
println!("\n .. generating blocks, syncing wallets .. ");
150+
generate_blocks_and_wait(6);
151+
node_a.sync_wallets().unwrap();
152+
node_b.sync_wallets().unwrap();
153+
154+
expect_event!(node_a, ChannelReady);
155+
156+
let channel_id = match *node_b.next_event() {
157+
ref e @ Event::ChannelReady { channel_id, .. } => {
158+
println!("{} got event {:?}", std::stringify!(node_b), e);
159+
node_b.event_handled();
160+
channel_id
161+
}
162+
ref e => {
163+
panic!("{} got unexpected event!: {:?}", std::stringify!(node_b), e);
164+
}
165+
};
166+
167+
println!("\nB receive_payment");
168+
let invoice = node_b.receive_payment(Some(1000), &"asdf", 9217).unwrap();
169+
170+
println!("\nA send_payment");
171+
node_a.send_payment(invoice).unwrap();
172+
173+
expect_event!(node_a, PaymentSuccessful);
174+
expect_event!(node_b, PaymentReceived);
175+
176+
node_b.close_channel(&channel_id, &node_a.node_id().unwrap()).unwrap();
177+
generate_blocks_and_wait(1);
178+
179+
expect_event!(node_a, ChannelClosed);
180+
expect_event!(node_b, ChannelClosed);
181+
182+
node_a.stop().unwrap();
183+
println!("\nA stopped");
184+
node_b.stop().unwrap();
185+
println!("\nB stopped");
186+
}

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)