Skip to content

Commit 3b1be8d

Browse files
committed
Move test bitcoind/electrsd out of OnceCell
`OnceCell` doesn't call `drop`, which makes the spawned `bitcoind`/`electrsd` instances linger around after our tests have finished. To fix this, we move them out of `OnceCell` and let every test that needs them spawn their own instances. This additional let us drop the `OnceCell` dev dependency. Additionally, we improve the test robustness by applying most of the changes from lightningdevkit/rust-lightning#2033.
1 parent e97c480 commit 3b1be8d

File tree

3 files changed

+158
-144
lines changed

3 files changed

+158
-144
lines changed

Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ libc = "0.2"
6565
[dev-dependencies]
6666
electrsd = { version = "0.22.0", features = ["legacy", "esplora_a33e97e1", "bitcoind_23_0"] }
6767
electrum-client = "0.12.0"
68-
once_cell = "1.16.0"
6968
proptest = "1.0.0"
7069

7170
[profile.release]

src/test/functional_tests.rs

+27-143
Original file line numberDiff line numberDiff line change
@@ -1,144 +1,15 @@
1+
use crate::test::utils::*;
12
use crate::test::utils::{expect_event, random_config};
23
use crate::{Builder, Error, Event, PaymentDirection, PaymentStatus};
34

4-
use bitcoin::{Address, Amount, OutPoint, Txid};
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;
5+
use bitcoin::Amount;
96

10-
use once_cell::sync::OnceCell;
11-
12-
use std::env;
13-
use std::sync::Mutex;
147
use std::time::Duration;
15-
16-
static BITCOIND: OnceCell<BitcoinD> = OnceCell::new();
17-
static ELECTRSD: OnceCell<ElectrsD> = OnceCell::new();
18-
static PREMINE: OnceCell<()> = OnceCell::new();
19-
static MINER_LOCK: Mutex<()> = Mutex::new(());
20-
21-
fn get_bitcoind() -> &'static BitcoinD {
22-
BITCOIND.get_or_init(|| {
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-
}
32-
33-
fn get_electrsd() -> &'static ElectrsD {
34-
ELECTRSD.get_or_init(|| {
35-
let bitcoind = get_bitcoind();
36-
let electrs_exe =
37-
env::var("ELECTRS_EXE").ok().or_else(electrsd::downloaded_exe_path).expect(
38-
"you need to provide env var ELECTRS_EXE or specify an electrsd version feature",
39-
);
40-
let mut conf = electrsd::Conf::default();
41-
conf.http_enabled = true;
42-
conf.network = "regtest";
43-
ElectrsD::with_conf(electrs_exe, &bitcoind, &conf).unwrap()
44-
})
45-
}
46-
47-
fn generate_blocks_and_wait(num: usize) {
48-
let _miner = MINER_LOCK.lock().unwrap();
49-
let cur_height = get_bitcoind().client.get_block_count().unwrap();
50-
let address =
51-
get_bitcoind().client.get_new_address(Some("test"), Some(AddressType::Legacy)).unwrap();
52-
let _block_hashes = get_bitcoind().client.generate_to_address(num as u64, &address).unwrap();
53-
wait_for_block(cur_height as usize + num);
54-
}
55-
56-
fn wait_for_block(min_height: usize) {
57-
let mut header = get_electrsd().client.block_headers_subscribe().unwrap();
58-
loop {
59-
if header.height >= min_height {
60-
break;
61-
}
62-
header = exponential_backoff_poll(|| {
63-
get_electrsd().trigger().unwrap();
64-
get_electrsd().client.ping().unwrap();
65-
get_electrsd().client.block_headers_pop().unwrap()
66-
});
67-
}
68-
}
69-
70-
fn wait_for_tx(txid: Txid) {
71-
let mut tx_res = get_electrsd().client.transaction_get(&txid);
72-
loop {
73-
if tx_res.is_ok() {
74-
break;
75-
}
76-
tx_res = exponential_backoff_poll(|| {
77-
get_electrsd().trigger().unwrap();
78-
get_electrsd().client.ping().unwrap();
79-
Some(get_electrsd().client.transaction_get(&txid))
80-
});
81-
}
82-
}
83-
84-
fn wait_for_outpoint_spend(outpoint: OutPoint) {
85-
let tx = get_electrsd().client.transaction_get(&outpoint.txid).unwrap();
86-
let txout_script = tx.output.get(outpoint.vout as usize).unwrap().clone().script_pubkey;
87-
let mut is_spent = !get_electrsd().client.script_get_history(&txout_script).unwrap().is_empty();
88-
loop {
89-
if is_spent {
90-
break;
91-
}
92-
93-
is_spent = exponential_backoff_poll(|| {
94-
get_electrsd().trigger().unwrap();
95-
get_electrsd().client.ping().unwrap();
96-
Some(!get_electrsd().client.script_get_history(&txout_script).unwrap().is_empty())
97-
});
98-
}
99-
}
100-
101-
fn exponential_backoff_poll<T, F>(mut poll: F) -> T
102-
where
103-
F: FnMut() -> Option<T>,
104-
{
105-
let mut delay = Duration::from_millis(64);
106-
let mut tries = 0;
107-
loop {
108-
match poll() {
109-
Some(data) => break data,
110-
None if delay.as_millis() < 512 => {
111-
delay = delay.mul_f32(2.0);
112-
}
113-
114-
None => {}
115-
}
116-
assert!(tries < 10, "Reached max tries.");
117-
tries += 1;
118-
std::thread::sleep(delay);
119-
}
120-
}
121-
122-
fn premine_and_distribute_funds(addrs: Vec<Address>, amount: Amount) {
123-
PREMINE.get_or_init(|| {
124-
generate_blocks_and_wait(101);
125-
});
126-
127-
for addr in addrs {
128-
let txid = get_bitcoind()
129-
.client
130-
.send_to_address(&addr, amount, None, None, None, None, None, None)
131-
.unwrap();
132-
wait_for_tx(txid);
133-
}
134-
135-
generate_blocks_and_wait(1);
136-
}
137-
1388
#[test]
1399
fn channel_full_cycle() {
10+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
14011
println!("== Node A ==");
141-
let esplora_url = get_electrsd().esplora_url.as_ref().unwrap();
12+
let esplora_url = electrsd.esplora_url.as_ref().unwrap();
14213
let config_a = random_config(esplora_url);
14314
let node_a = Builder::from_config(config_a).build();
14415
node_a.start().unwrap();
@@ -150,7 +21,12 @@ fn channel_full_cycle() {
15021
node_b.start().unwrap();
15122
let addr_b = node_b.new_funding_address().unwrap();
15223

153-
premine_and_distribute_funds(vec![addr_a, addr_b], Amount::from_sat(100000));
24+
premine_and_distribute_funds(
25+
&bitcoind,
26+
&electrsd,
27+
vec![addr_a, addr_b],
28+
Amount::from_sat(100000),
29+
);
15430
node_a.sync_wallets().unwrap();
15531
node_b.sync_wallets().unwrap();
15632
assert_eq!(node_a.on_chain_balance().unwrap().get_spendable(), 100000);
@@ -170,10 +46,10 @@ fn channel_full_cycle() {
17046
}
17147
};
17248

173-
wait_for_tx(funding_txo.txid);
49+
wait_for_tx(&electrsd, funding_txo.txid);
17450

17551
println!("\n .. generating blocks, syncing wallets .. ");
176-
generate_blocks_and_wait(6);
52+
generate_blocks_and_wait(&bitcoind, &electrsd, 6);
17753
node_a.sync_wallets().unwrap();
17854
node_b.sync_wallets().unwrap();
17955

@@ -276,9 +152,9 @@ fn channel_full_cycle() {
276152
expect_event!(node_a, ChannelClosed);
277153
expect_event!(node_b, ChannelClosed);
278154

279-
wait_for_outpoint_spend(funding_txo.into_bitcoin_outpoint());
155+
wait_for_outpoint_spend(&electrsd, funding_txo.into_bitcoin_outpoint());
280156

281-
generate_blocks_and_wait(1);
157+
generate_blocks_and_wait(&bitcoind, &electrsd, 1);
282158
node_a.sync_wallets().unwrap();
283159
node_b.sync_wallets().unwrap();
284160

@@ -293,8 +169,9 @@ fn channel_full_cycle() {
293169

294170
#[test]
295171
fn channel_open_fails_when_funds_insufficient() {
172+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
296173
println!("== Node A ==");
297-
let esplora_url = get_electrsd().esplora_url.as_ref().unwrap();
174+
let esplora_url = electrsd.esplora_url.as_ref().unwrap();
298175
let config_a = random_config(&esplora_url);
299176
let node_a = Builder::from_config(config_a).build();
300177
node_a.start().unwrap();
@@ -306,7 +183,12 @@ fn channel_open_fails_when_funds_insufficient() {
306183
node_b.start().unwrap();
307184
let addr_b = node_b.new_funding_address().unwrap();
308185

309-
premine_and_distribute_funds(vec![addr_a, addr_b], Amount::from_sat(100000));
186+
premine_and_distribute_funds(
187+
&bitcoind,
188+
&electrsd,
189+
vec![addr_a, addr_b],
190+
Amount::from_sat(100000),
191+
);
310192
node_a.sync_wallets().unwrap();
311193
node_b.sync_wallets().unwrap();
312194
assert_eq!(node_a.on_chain_balance().unwrap().get_spendable(), 100000);
@@ -322,7 +204,8 @@ fn channel_open_fails_when_funds_insufficient() {
322204

323205
#[test]
324206
fn connect_to_public_testnet_esplora() {
325-
let esplora_url = get_electrsd().esplora_url.as_ref().unwrap();
207+
let (_bitcoind, electrsd) = setup_bitcoind_and_electrsd();
208+
let esplora_url = electrsd.esplora_url.as_ref().unwrap();
326209
let mut config = random_config(&esplora_url);
327210
config.esplora_server_url = "https://blockstream.info/testnet/api".to_string();
328211
config.network = bitcoin::Network::Testnet;
@@ -334,15 +217,16 @@ fn connect_to_public_testnet_esplora() {
334217

335218
#[test]
336219
fn start_stop_reinit() {
337-
let esplora_url = get_electrsd().esplora_url.as_ref().unwrap();
220+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
221+
let esplora_url = electrsd.esplora_url.as_ref().unwrap();
338222
let config = random_config(&esplora_url);
339223
let node = Builder::from_config(config.clone()).build();
340224
let expected_node_id = node.node_id();
341225

342226
let funding_address = node.new_funding_address().unwrap();
343227
let expected_amount = Amount::from_sat(100000);
344228

345-
premine_and_distribute_funds(vec![funding_address], expected_amount);
229+
premine_and_distribute_funds(&bitcoind, &electrsd, vec![funding_address], expected_amount);
346230
assert_eq!(node.on_chain_balance().unwrap().get_total(), 0);
347231

348232
node.start().unwrap();

src/test/utils.rs

+131
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,20 @@ use crate::Config;
33
use lightning::util::persist::KVStorePersister;
44
use lightning::util::ser::Writeable;
55

6+
use bitcoin::{Address, Amount, OutPoint, Txid};
7+
8+
use bitcoind::bitcoincore_rpc::RpcApi;
9+
use electrsd::bitcoind::bitcoincore_rpc::bitcoincore_rpc_json::AddressType;
10+
use electrsd::{bitcoind, bitcoind::BitcoinD, ElectrsD};
11+
use electrum_client::ElectrumApi;
12+
613
use rand::distributions::Alphanumeric;
714
use rand::{thread_rng, Rng};
815
use std::collections::HashMap;
16+
use std::env;
917
use std::sync::atomic::{AtomicBool, Ordering};
1018
use std::sync::Mutex;
19+
use std::time::Duration;
1120

1221
macro_rules! expect_event {
1322
($node: expr, $event_type: ident) => {{
@@ -94,3 +103,125 @@ pub fn random_config(esplora_url: &str) -> Config {
94103

95104
config
96105
}
106+
107+
pub fn setup_bitcoind_and_electrsd() -> (BitcoinD, ElectrsD) {
108+
let bitcoind_exe =
109+
env::var("BITCOIND_EXE").ok().or_else(|| bitcoind::downloaded_exe_path().ok()).expect(
110+
"you need to provide an env var BITCOIND_EXE or specify a bitcoind version feature",
111+
);
112+
let mut bitcoind_conf = bitcoind::Conf::default();
113+
bitcoind_conf.network = "regtest";
114+
let bitcoind = BitcoinD::with_conf(bitcoind_exe, &bitcoind_conf).unwrap();
115+
116+
let electrs_exe = env::var("ELECTRS_EXE")
117+
.ok()
118+
.or_else(electrsd::downloaded_exe_path)
119+
.expect("you need to provide env var ELECTRS_EXE or specify an electrsd version feature");
120+
let mut electrsd_conf = electrsd::Conf::default();
121+
electrsd_conf.http_enabled = true;
122+
electrsd_conf.network = "regtest";
123+
let electrsd = ElectrsD::with_conf(electrs_exe, &bitcoind, &electrsd_conf).unwrap();
124+
(bitcoind, electrsd)
125+
}
126+
127+
pub fn generate_blocks_and_wait(bitcoind: &BitcoinD, electrsd: &ElectrsD, num: usize) {
128+
let cur_height = bitcoind.client.get_block_count().expect("failed to get current block height");
129+
let address = bitcoind
130+
.client
131+
.get_new_address(Some("test"), Some(AddressType::Legacy))
132+
.expect("failed to get new address");
133+
// TODO: expect this Result once the WouldBlock issue is resolved upstream.
134+
let _block_hashes_res = bitcoind.client.generate_to_address(num as u64, &address);
135+
wait_for_block(electrsd, cur_height as usize + num);
136+
}
137+
138+
pub fn wait_for_block(electrsd: &ElectrsD, min_height: usize) {
139+
let mut header = match electrsd.client.block_headers_subscribe() {
140+
Ok(header) => header,
141+
Err(_) => {
142+
// While subscribing should succeed the first time around, we ran into some cases where
143+
// it didn't. Since we can't proceed without subscribing, we try again after a delay
144+
// and panic if it still fails.
145+
std::thread::sleep(Duration::from_secs(1));
146+
electrsd.client.block_headers_subscribe().expect("failed to subscribe to block headers")
147+
}
148+
};
149+
loop {
150+
if header.height >= min_height {
151+
break;
152+
}
153+
header = exponential_backoff_poll(|| {
154+
electrsd.trigger().expect("failed to trigger electrsd");
155+
electrsd.client.ping().expect("failed to ping electrsd");
156+
electrsd.client.block_headers_pop().expect("failed to pop block header")
157+
});
158+
}
159+
}
160+
161+
pub fn wait_for_tx(electrsd: &ElectrsD, txid: Txid) {
162+
let mut tx_res = electrsd.client.transaction_get(&txid);
163+
loop {
164+
if tx_res.is_ok() {
165+
break;
166+
}
167+
tx_res = exponential_backoff_poll(|| {
168+
electrsd.trigger().unwrap();
169+
electrsd.client.ping().unwrap();
170+
Some(electrsd.client.transaction_get(&txid))
171+
});
172+
}
173+
}
174+
175+
pub fn wait_for_outpoint_spend(electrsd: &ElectrsD, outpoint: OutPoint) {
176+
let tx = electrsd.client.transaction_get(&outpoint.txid).unwrap();
177+
let txout_script = tx.output.get(outpoint.vout as usize).unwrap().clone().script_pubkey;
178+
let mut is_spent = !electrsd.client.script_get_history(&txout_script).unwrap().is_empty();
179+
loop {
180+
if is_spent {
181+
break;
182+
}
183+
184+
is_spent = exponential_backoff_poll(|| {
185+
electrsd.trigger().unwrap();
186+
electrsd.client.ping().unwrap();
187+
Some(!electrsd.client.script_get_history(&txout_script).unwrap().is_empty())
188+
});
189+
}
190+
}
191+
192+
pub fn exponential_backoff_poll<T, F>(mut poll: F) -> T
193+
where
194+
F: FnMut() -> Option<T>,
195+
{
196+
let mut delay = Duration::from_millis(64);
197+
let mut tries = 0;
198+
loop {
199+
match poll() {
200+
Some(data) => break data,
201+
None if delay.as_millis() < 512 => {
202+
delay = delay.mul_f32(2.0);
203+
}
204+
205+
None => {}
206+
}
207+
assert!(tries < 10, "Reached max tries.");
208+
tries += 1;
209+
std::thread::sleep(delay);
210+
}
211+
}
212+
213+
pub fn premine_and_distribute_funds(
214+
bitcoind: &BitcoinD, electrsd: &ElectrsD, addrs: Vec<Address>, amount: Amount,
215+
) {
216+
generate_blocks_and_wait(bitcoind, electrsd, 101);
217+
218+
for addr in addrs {
219+
let txid = bitcoind
220+
.client
221+
.send_to_address(&addr, amount, None, None, None, None, None, None)
222+
.unwrap();
223+
wait_for_tx(electrsd, txid);
224+
}
225+
226+
generate_blocks_and_wait(bitcoind, electrsd, 1);
227+
}

0 commit comments

Comments
 (0)