forked from lightningdevkit/rust-lightning
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathintegration_tests.rs
350 lines (293 loc) · 12.2 KB
/
integration_tests.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
#![cfg(any(feature = "esplora-blocking", feature = "esplora-async"))]
use lightning_transaction_sync::EsploraSyncClient;
use lightning::chain::{Confirm, Filter};
use lightning::chain::transaction::TransactionData;
use lightning::util::logger::{Logger, Record};
use electrsd::{bitcoind, bitcoind::BitcoinD, ElectrsD};
use bitcoin::{Amount, Txid, BlockHash, BlockHeader};
use bitcoin::blockdata::constants::genesis_block;
use bitcoin::network::constants::Network;
use electrsd::bitcoind::bitcoincore_rpc::bitcoincore_rpc_json::AddressType;
use bitcoind::bitcoincore_rpc::RpcApi;
use electrum_client::ElectrumApi;
use once_cell::sync::OnceCell;
use std::env;
use std::sync::Mutex;
use std::time::Duration;
use std::collections::{HashMap, HashSet};
static BITCOIND: OnceCell<BitcoinD> = OnceCell::new();
static ELECTRSD: OnceCell<ElectrsD> = OnceCell::new();
static PREMINE: OnceCell<()> = OnceCell::new();
static MINER_LOCK: OnceCell<Mutex<()>> = OnceCell::new();
fn get_bitcoind() -> &'static BitcoinD {
BITCOIND.get_or_init(|| {
let bitcoind_exe =
env::var("BITCOIND_EXE").ok().or_else(|| bitcoind::downloaded_exe_path().ok()).expect(
"you need to provide an env var BITCOIND_EXE or specify a bitcoind version feature",
);
let mut conf = bitcoind::Conf::default();
conf.network = "regtest";
let bitcoind = BitcoinD::with_conf(bitcoind_exe, &conf).unwrap();
std::thread::sleep(Duration::from_secs(1));
bitcoind
})
}
fn get_electrsd() -> &'static ElectrsD {
ELECTRSD.get_or_init(|| {
let bitcoind = get_bitcoind();
let electrs_exe =
env::var("ELECTRS_EXE").ok().or_else(electrsd::downloaded_exe_path).expect(
"you need to provide env var ELECTRS_EXE or specify an electrsd version feature",
);
let mut conf = electrsd::Conf::default();
conf.http_enabled = true;
conf.network = "regtest";
let electrsd = ElectrsD::with_conf(electrs_exe, &bitcoind, &conf).unwrap();
std::thread::sleep(Duration::from_secs(1));
electrsd
})
}
fn generate_blocks_and_wait(num: usize) {
let miner_lock = MINER_LOCK.get_or_init(|| Mutex::new(()));
let _miner = miner_lock.lock().unwrap();
let cur_height = get_bitcoind().client.get_block_count().expect("failed to get current block height");
let address = get_bitcoind().client.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 = get_bitcoind().client.generate_to_address(num as u64, &address);
wait_for_block(cur_height as usize + num);
}
fn wait_for_block(min_height: usize) {
let mut header = match get_electrsd().client.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));
get_electrsd().client.block_headers_subscribe().expect("failed to subscribe to block headers")
}
};
loop {
if header.height >= min_height {
break;
}
header = exponential_backoff_poll(|| {
get_electrsd().trigger().expect("failed to trigger electrsd");
get_electrsd().client.ping().expect("failed to ping electrsd");
get_electrsd().client.block_headers_pop().expect("failed to pop block header")
});
}
}
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);
tries += 1;
}
None if tries == 10 => panic!("Exceeded our maximum wait time."),
None => tries += 1,
}
std::thread::sleep(delay);
}
}
fn premine() {
PREMINE.get_or_init(|| {
generate_blocks_and_wait(101);
});
}
#[derive(Debug)]
enum TestConfirmableEvent {
Confirmed(Txid, BlockHash, u32),
Unconfirmed(Txid),
BestBlockUpdated(BlockHash, u32),
}
struct TestConfirmable {
pub confirmed_txs: Mutex<HashMap<Txid, (BlockHash, u32)>>,
pub unconfirmed_txs: Mutex<HashSet<Txid>>,
pub best_block: Mutex<(BlockHash, u32)>,
pub events: Mutex<Vec<TestConfirmableEvent>>,
}
impl TestConfirmable {
pub fn new() -> Self {
let genesis_hash = genesis_block(Network::Regtest).block_hash();
Self {
confirmed_txs: Mutex::new(HashMap::new()),
unconfirmed_txs: Mutex::new(HashSet::new()),
best_block: Mutex::new((genesis_hash, 0)),
events: Mutex::new(Vec::new()),
}
}
}
impl Confirm for TestConfirmable {
fn transactions_confirmed(&self, header: &BlockHeader, txdata: &TransactionData<'_>, height: u32) {
for (_, tx) in txdata {
let txid = tx.txid();
let block_hash = header.block_hash();
self.confirmed_txs.lock().unwrap().insert(txid, (block_hash, height));
self.unconfirmed_txs.lock().unwrap().remove(&txid);
self.events.lock().unwrap().push(TestConfirmableEvent::Confirmed(txid, block_hash, height));
}
}
fn transaction_unconfirmed(&self, txid: &Txid) {
self.unconfirmed_txs.lock().unwrap().insert(*txid);
self.confirmed_txs.lock().unwrap().remove(txid);
self.events.lock().unwrap().push(TestConfirmableEvent::Unconfirmed(*txid));
}
fn best_block_updated(&self, header: &BlockHeader, height: u32) {
let block_hash = header.block_hash();
*self.best_block.lock().unwrap() = (block_hash, height);
self.events.lock().unwrap().push(TestConfirmableEvent::BestBlockUpdated(block_hash, height));
}
fn get_relevant_txids(&self) -> Vec<(Txid, Option<BlockHash>)> {
self.confirmed_txs.lock().unwrap().iter().map(|(&txid, (hash, _))| (txid, Some(*hash))).collect::<Vec<_>>()
}
}
pub struct TestLogger {}
impl Logger for TestLogger {
fn log(&self, record: &Record) {
println!("{} -- {}",
record.level,
record.args);
}
}
#[test]
#[cfg(feature = "esplora-blocking")]
fn test_esplora_syncs() {
premine();
let mut logger = TestLogger {};
let esplora_url = format!("http://{}", get_electrsd().esplora_url.as_ref().unwrap());
let tx_sync = EsploraSyncClient::new(esplora_url, &mut logger);
let confirmable = TestConfirmable::new();
// Check we pick up on new best blocks
let expected_height = 0u32;
assert_eq!(confirmable.best_block.lock().unwrap().1, expected_height);
tx_sync.sync(vec![&confirmable]).unwrap();
let expected_height = get_bitcoind().client.get_block_count().unwrap() as u32;
assert_eq!(confirmable.best_block.lock().unwrap().1, expected_height);
let events = std::mem::take(&mut *confirmable.events.lock().unwrap());
assert_eq!(events.len(), 1);
// Check registered confirmed transactions are marked confirmed
let new_address = get_bitcoind().client.get_new_address(Some("test"), Some(AddressType::Legacy)).unwrap();
let txid = get_bitcoind().client.send_to_address(&new_address, Amount::from_sat(5000), None, None, None, None, None, None).unwrap();
tx_sync.register_tx(&txid, &new_address.script_pubkey());
tx_sync.sync(vec![&confirmable]).unwrap();
let events = std::mem::take(&mut *confirmable.events.lock().unwrap());
assert_eq!(events.len(), 0);
assert!(confirmable.confirmed_txs.lock().unwrap().is_empty());
assert!(confirmable.unconfirmed_txs.lock().unwrap().is_empty());
generate_blocks_and_wait(1);
tx_sync.sync(vec![&confirmable]).unwrap();
let events = std::mem::take(&mut *confirmable.events.lock().unwrap());
assert_eq!(events.len(), 2);
assert!(confirmable.confirmed_txs.lock().unwrap().contains_key(&txid));
assert!(confirmable.unconfirmed_txs.lock().unwrap().is_empty());
// Check previously confirmed transactions are marked unconfirmed when they are reorged.
let best_block_hash = get_bitcoind().client.get_best_block_hash().unwrap();
get_bitcoind().client.invalidate_block(&best_block_hash).unwrap();
// We're getting back to the previous height with a new tip, but best block shouldn't change.
generate_blocks_and_wait(1);
assert_ne!(get_bitcoind().client.get_best_block_hash().unwrap(), best_block_hash);
tx_sync.sync(vec![&confirmable]).unwrap();
let events = std::mem::take(&mut *confirmable.events.lock().unwrap());
assert_eq!(events.len(), 0);
// Now we're surpassing previous height, getting new tip.
generate_blocks_and_wait(1);
assert_ne!(get_bitcoind().client.get_best_block_hash().unwrap(), best_block_hash);
tx_sync.sync(vec![&confirmable]).unwrap();
// Transaction still confirmed but under new tip.
assert!(confirmable.confirmed_txs.lock().unwrap().contains_key(&txid));
assert!(confirmable.unconfirmed_txs.lock().unwrap().is_empty());
// Check we got unconfirmed, then reconfirmed in the meantime.
let events = std::mem::take(&mut *confirmable.events.lock().unwrap());
assert_eq!(events.len(), 3);
match events[0] {
TestConfirmableEvent::Unconfirmed(t) => {
assert_eq!(t, txid);
},
_ => panic!("Unexpected event"),
}
match events[1] {
TestConfirmableEvent::BestBlockUpdated(..) => {},
_ => panic!("Unexpected event"),
}
match events[2] {
TestConfirmableEvent::Confirmed(t, _, _) => {
assert_eq!(t, txid);
},
_ => panic!("Unexpected event"),
}
}
#[tokio::test]
#[cfg(feature = "esplora-async")]
async fn test_esplora_syncs() {
premine();
let mut logger = TestLogger {};
let esplora_url = format!("http://{}", get_electrsd().esplora_url.as_ref().unwrap());
let tx_sync = EsploraSyncClient::new(esplora_url, &mut logger);
let confirmable = TestConfirmable::new();
// Check we pick up on new best blocks
let expected_height = 0u32;
assert_eq!(confirmable.best_block.lock().unwrap().1, expected_height);
tx_sync.sync(vec![&confirmable]).await.unwrap();
let expected_height = get_bitcoind().client.get_block_count().unwrap() as u32;
assert_eq!(confirmable.best_block.lock().unwrap().1, expected_height);
let events = std::mem::take(&mut *confirmable.events.lock().unwrap());
assert_eq!(events.len(), 1);
// Check registered confirmed transactions are marked confirmed
let new_address = get_bitcoind().client.get_new_address(Some("test"), Some(AddressType::Legacy)).unwrap();
let txid = get_bitcoind().client.send_to_address(&new_address, Amount::from_sat(5000), None, None, None, None, None, None).unwrap();
tx_sync.register_tx(&txid, &new_address.script_pubkey());
tx_sync.sync(vec![&confirmable]).await.unwrap();
let events = std::mem::take(&mut *confirmable.events.lock().unwrap());
assert_eq!(events.len(), 0);
assert!(confirmable.confirmed_txs.lock().unwrap().is_empty());
assert!(confirmable.unconfirmed_txs.lock().unwrap().is_empty());
generate_blocks_and_wait(1);
tx_sync.sync(vec![&confirmable]).await.unwrap();
let events = std::mem::take(&mut *confirmable.events.lock().unwrap());
assert_eq!(events.len(), 2);
assert!(confirmable.confirmed_txs.lock().unwrap().contains_key(&txid));
assert!(confirmable.unconfirmed_txs.lock().unwrap().is_empty());
// Check previously confirmed transactions are marked unconfirmed when they are reorged.
let best_block_hash = get_bitcoind().client.get_best_block_hash().unwrap();
get_bitcoind().client.invalidate_block(&best_block_hash).unwrap();
// We're getting back to the previous height with a new tip, but best block shouldn't change.
generate_blocks_and_wait(1);
assert_ne!(get_bitcoind().client.get_best_block_hash().unwrap(), best_block_hash);
tx_sync.sync(vec![&confirmable]).await.unwrap();
let events = std::mem::take(&mut *confirmable.events.lock().unwrap());
assert_eq!(events.len(), 0);
// Now we're surpassing previous height, getting new tip.
generate_blocks_and_wait(1);
assert_ne!(get_bitcoind().client.get_best_block_hash().unwrap(), best_block_hash);
tx_sync.sync(vec![&confirmable]).await.unwrap();
// Transaction still confirmed but under new tip.
assert!(confirmable.confirmed_txs.lock().unwrap().contains_key(&txid));
assert!(confirmable.unconfirmed_txs.lock().unwrap().is_empty());
// Check we got unconfirmed, then reconfirmed in the meantime.
let events = std::mem::take(&mut *confirmable.events.lock().unwrap());
assert_eq!(events.len(), 3);
match events[0] {
TestConfirmableEvent::Unconfirmed(t) => {
assert_eq!(t, txid);
},
_ => panic!("Unexpected event"),
}
match events[1] {
TestConfirmableEvent::BestBlockUpdated(..) => {},
_ => panic!("Unexpected event"),
}
match events[2] {
TestConfirmableEvent::Confirmed(t, _, _) => {
assert_eq!(t, txid);
},
_ => panic!("Unexpected event"),
}
}