|
| 1 | +use bdk_chain::local_chain::LocalChain; |
| 2 | +use bdk_chain::BlockId; |
1 | 3 | use bdk_esplora::EsploraExt; |
2 | 4 | use electrsd::bitcoind::bitcoincore_rpc::RpcApi; |
3 | 5 | use electrsd::bitcoind::{self, anyhow, BitcoinD}; |
4 | 6 | use electrsd::{Conf, ElectrsD}; |
5 | 7 | use esplora_client::{self, BlockingClient, Builder}; |
6 | | -use std::collections::{BTreeMap, HashSet}; |
| 8 | +use std::collections::{BTreeMap, BTreeSet, HashSet}; |
7 | 9 | use std::str::FromStr; |
8 | 10 | use std::thread::sleep; |
9 | 11 | use std::time::Duration; |
10 | 12 |
|
11 | 13 | use bdk_chain::bitcoin::{Address, Amount, BlockHash, Txid}; |
12 | 14 |
|
| 15 | +macro_rules! h { |
| 16 | + ($index:literal) => {{ |
| 17 | + bdk_chain::bitcoin::hashes::Hash::hash($index.as_bytes()) |
| 18 | + }}; |
| 19 | +} |
| 20 | + |
| 21 | +macro_rules! local_chain { |
| 22 | + [ $(($height:expr, $block_hash:expr)), * ] => {{ |
| 23 | + #[allow(unused_mut)] |
| 24 | + bdk_chain::local_chain::LocalChain::from_blocks([$(($height, $block_hash).into()),*].into_iter().collect()) |
| 25 | + .expect("chain must have genesis block") |
| 26 | + }}; |
| 27 | +} |
| 28 | + |
13 | 29 | struct TestEnv { |
14 | 30 | bitcoind: BitcoinD, |
15 | 31 | #[allow(dead_code)] |
@@ -39,6 +55,20 @@ impl TestEnv { |
39 | 55 | }) |
40 | 56 | } |
41 | 57 |
|
| 58 | + fn reset_electrsd(mut self) -> anyhow::Result<Self> { |
| 59 | + let mut electrs_conf = Conf::default(); |
| 60 | + electrs_conf.http_enabled = true; |
| 61 | + let electrs_exe = |
| 62 | + electrsd::downloaded_exe_path().expect("electrs version feature must be enabled"); |
| 63 | + let electrsd = ElectrsD::with_conf(electrs_exe, &self.bitcoind, &electrs_conf)?; |
| 64 | + |
| 65 | + let base_url = format!("http://{}", &electrsd.esplora_url.clone().unwrap()); |
| 66 | + let client = Builder::new(base_url.as_str()).build_blocking()?; |
| 67 | + self.electrsd = electrsd; |
| 68 | + self.client = client; |
| 69 | + Ok(self) |
| 70 | + } |
| 71 | + |
42 | 72 | fn mine_blocks( |
43 | 73 | &self, |
44 | 74 | count: usize, |
@@ -202,3 +232,136 @@ pub fn test_update_tx_graph_gap_limit() -> anyhow::Result<()> { |
202 | 232 |
|
203 | 233 | Ok(()) |
204 | 234 | } |
| 235 | + |
| 236 | +#[test] |
| 237 | +fn update_local_chain() -> anyhow::Result<()> { |
| 238 | + const TIP_HEIGHT: u32 = 50; |
| 239 | + |
| 240 | + let env = TestEnv::new()?; |
| 241 | + let b = { |
| 242 | + let bdc = &env.bitcoind.client; |
| 243 | + assert_eq!(bdc.get_block_count()?, 1); |
| 244 | + [(0, bdc.get_block_hash(0)?), (1, bdc.get_block_hash(1)?)] |
| 245 | + .into_iter() |
| 246 | + .chain((2..).zip(env.mine_blocks((TIP_HEIGHT - 1) as usize, None)?)) |
| 247 | + .collect::<BTreeMap<_, _>>() |
| 248 | + }; |
| 249 | + // so new blocks can be seen by Electrs |
| 250 | + let env = env.reset_electrsd()?; |
| 251 | + |
| 252 | + struct TestCase { |
| 253 | + name: &'static str, |
| 254 | + chain: LocalChain, |
| 255 | + heights: &'static [u32], |
| 256 | + exp_update_heights: &'static [u32], |
| 257 | + } |
| 258 | + |
| 259 | + let test_cases = [ |
| 260 | + TestCase { |
| 261 | + name: "request_later_blocks", |
| 262 | + chain: local_chain![(0, b[&0]), (21, b[&21])], |
| 263 | + heights: &[22, 25, 28], |
| 264 | + exp_update_heights: &[21, 22, 25, 28, TIP_HEIGHT], |
| 265 | + }, |
| 266 | + TestCase { |
| 267 | + name: "request_prev_blocks", |
| 268 | + chain: local_chain![(0, b[&0]), (1, b[&1]), (5, b[&5])], |
| 269 | + heights: &[4], |
| 270 | + exp_update_heights: &[4, 5, TIP_HEIGHT], |
| 271 | + }, |
| 272 | + TestCase { |
| 273 | + name: "request_prev_blocks_2", |
| 274 | + chain: local_chain![(0, b[&0]), (1, b[&1]), (10, b[&10])], |
| 275 | + heights: &[4, 6], |
| 276 | + exp_update_heights: &[4, 6, 10, TIP_HEIGHT], |
| 277 | + }, |
| 278 | + TestCase { |
| 279 | + name: "request_later_and_prev_blocks", |
| 280 | + chain: local_chain![(0, b[&0]), (7, b[&7]), (11, b[&11])], |
| 281 | + heights: &[8, 9, 15], |
| 282 | + exp_update_heights: &[8, 9, 11, 15, TIP_HEIGHT], |
| 283 | + }, |
| 284 | + TestCase { |
| 285 | + name: "request_tip_only", |
| 286 | + chain: local_chain![(0, b[&0]), (5, b[&5]), (49, b[&49])], |
| 287 | + heights: &[TIP_HEIGHT], |
| 288 | + exp_update_heights: &[49, TIP_HEIGHT], |
| 289 | + }, |
| 290 | + TestCase { |
| 291 | + name: "request_nothing", |
| 292 | + chain: local_chain![(0, b[&0]), (13, b[&13]), (23, b[&23])], |
| 293 | + heights: &[], |
| 294 | + exp_update_heights: &[23, TIP_HEIGHT], |
| 295 | + }, |
| 296 | + TestCase { |
| 297 | + name: "request_nothing_during_reorg", |
| 298 | + chain: local_chain![(0, b[&0]), (13, b[&13]), (23, h!("23"))], |
| 299 | + heights: &[], |
| 300 | + exp_update_heights: &[13, 23, TIP_HEIGHT], |
| 301 | + }, |
| 302 | + TestCase { |
| 303 | + name: "request_nothing_during_reorg_2", |
| 304 | + chain: local_chain![(0, b[&0]), (21, b[&21]), (22, h!("22")), (23, h!("23"))], |
| 305 | + heights: &[], |
| 306 | + exp_update_heights: &[21, 22, 23, TIP_HEIGHT], |
| 307 | + }, |
| 308 | + TestCase { |
| 309 | + name: "request_prev_blocks_during_reorg", |
| 310 | + chain: local_chain![(0, b[&0]), (21, b[&21]), (22, h!("22")), (23, h!("23"))], |
| 311 | + heights: &[17, 20], |
| 312 | + exp_update_heights: &[17, 20, 21, 22, 23, TIP_HEIGHT], |
| 313 | + }, |
| 314 | + TestCase { |
| 315 | + name: "request_later_blocks_during_reorg", |
| 316 | + chain: local_chain![(0, b[&0]), (9, b[&9]), (22, h!("22")), (23, h!("23"))], |
| 317 | + heights: &[25, 27], |
| 318 | + exp_update_heights: &[9, 22, 23, 25, 27, TIP_HEIGHT], |
| 319 | + }, |
| 320 | + TestCase { |
| 321 | + name: "request_later_blocks_during_reorg_2", |
| 322 | + chain: local_chain![(0, b[&0]), (9, h!("9"))], |
| 323 | + heights: &[10], |
| 324 | + exp_update_heights: &[0, 9, 10, TIP_HEIGHT], |
| 325 | + }, |
| 326 | + TestCase { |
| 327 | + name: "request_later_and_prev_blocks_during_reorg", |
| 328 | + chain: local_chain![(0, b[&0]), (1, b[&1]), (9, h!("9"))], |
| 329 | + heights: &[8, 11], |
| 330 | + exp_update_heights: &[1, 8, 9, 11, TIP_HEIGHT], |
| 331 | + }, |
| 332 | + ]; |
| 333 | + |
| 334 | + for (i, t) in test_cases.into_iter().enumerate() { |
| 335 | + println!("Case {} : '{}'", i, t.name); |
| 336 | + |
| 337 | + let update = env |
| 338 | + .client |
| 339 | + .update_local_chain(t.chain.tip(), t.heights.iter().copied()) |
| 340 | + .map_err(|err| { |
| 341 | + anyhow::format_err!("[{}:{}] `update_local_chain` failed: {}", i, t.name, err) |
| 342 | + })?; |
| 343 | + |
| 344 | + let update_blocks = update |
| 345 | + .tip |
| 346 | + .iter() |
| 347 | + .map(|cp| cp.block_id()) |
| 348 | + .collect::<BTreeSet<_>>(); |
| 349 | + |
| 350 | + let exp_update_blocks = t |
| 351 | + .exp_update_heights |
| 352 | + .iter() |
| 353 | + .map(|&height| { |
| 354 | + let hash = b[&height]; |
| 355 | + BlockId { height, hash } |
| 356 | + }) |
| 357 | + .collect::<BTreeSet<_>>(); |
| 358 | + |
| 359 | + assert_eq!( |
| 360 | + update_blocks, exp_update_blocks, |
| 361 | + "[{}:{}] unexpected update", |
| 362 | + i, t.name |
| 363 | + ); |
| 364 | + } |
| 365 | + |
| 366 | + Ok(()) |
| 367 | +} |
0 commit comments