Skip to content

Commit 519cd75

Browse files
committed
test(esplora): move esplora tests into src files
Since we want to keep these methods private.
1 parent a6e613e commit 519cd75

File tree

5 files changed

+579
-576
lines changed

5 files changed

+579
-576
lines changed

crates/esplora/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ miniscript = { version = "11.0.0", optional = true, default-features = false }
2525
bdk_testenv = { path = "../testenv", default_features = false }
2626
electrsd = { version= "0.27.1", features = ["bitcoind_25_0", "esplora_a33e97e1", "legacy"] }
2727
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
28+
anyhow = "1"
2829

2930
[features]
3031
default = ["std", "async-https", "blocking"]

crates/esplora/src/async_ext.rs

Lines changed: 185 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,7 @@ impl EsploraAsyncExt for esplora_client::AsyncClient {
139139
/// block-based chain-sources). Therefore it's better to be conservative when setting the tip (use
140140
/// an earlier tip rather than a later tip) otherwise the caller may accidentally skip blocks when
141141
/// alternating between chain-sources.
142-
#[doc(hidden)]
143-
pub async fn init_chain_update(
142+
async fn init_chain_update(
144143
client: &esplora_client::AsyncClient,
145144
local_tip: &CheckPoint,
146145
) -> Result<BTreeMap<u32, BlockHash>, Error> {
@@ -183,8 +182,7 @@ pub async fn init_chain_update(
183182
///
184183
/// A checkpoint is considered "missing" if an anchor (of `anchors`) points to a height without an
185184
/// existing checkpoint/block under `local_tip` or `update_blocks`.
186-
#[doc(hidden)]
187-
pub async fn finalize_chain_update<A: Anchor>(
185+
async fn finalize_chain_update<A: Anchor>(
188186
client: &esplora_client::AsyncClient,
189187
local_tip: &CheckPoint,
190188
anchors: &BTreeSet<(A, Txid)>,
@@ -243,8 +241,7 @@ pub async fn finalize_chain_update<A: Anchor>(
243241

244242
/// This performs a full scan to get an update for the [`TxGraph`] and
245243
/// [`KeychainTxOutIndex`](bdk_chain::keychain::KeychainTxOutIndex).
246-
#[doc(hidden)]
247-
pub async fn full_scan_for_index_and_graph<K: Ord + Clone + Send>(
244+
async fn full_scan_for_index_and_graph<K: Ord + Clone + Send>(
248245
client: &esplora_client::AsyncClient,
249246
keychain_spks: BTreeMap<
250247
K,
@@ -339,8 +336,7 @@ pub async fn full_scan_for_index_and_graph<K: Ord + Clone + Send>(
339336
Ok((graph, last_active_indexes))
340337
}
341338

342-
#[doc(hidden)]
343-
pub async fn sync_for_index_and_graph(
339+
async fn sync_for_index_and_graph(
344340
client: &esplora_client::AsyncClient,
345341
misc_spks: impl IntoIterator<IntoIter = impl Iterator<Item = ScriptBuf> + Send> + Send,
346342
txids: impl IntoIterator<IntoIter = impl Iterator<Item = Txid> + Send> + Send,
@@ -414,3 +410,184 @@ pub async fn sync_for_index_and_graph(
414410

415411
Ok(graph)
416412
}
413+
414+
#[cfg(test)]
415+
mod test {
416+
use std::{collections::BTreeSet, time::Duration};
417+
418+
use bdk_chain::{
419+
bitcoin::{hashes::Hash, Txid},
420+
local_chain::LocalChain,
421+
BlockId,
422+
};
423+
use bdk_testenv::TestEnv;
424+
use electrsd::bitcoind::bitcoincore_rpc::RpcApi;
425+
use esplora_client::Builder;
426+
427+
use crate::async_ext::{finalize_chain_update, init_chain_update};
428+
429+
macro_rules! h {
430+
($index:literal) => {{
431+
bdk_chain::bitcoin::hashes::Hash::hash($index.as_bytes())
432+
}};
433+
}
434+
435+
/// Ensure that update does not remove heights (from original), and all anchor heights are included.
436+
#[tokio::test]
437+
pub async fn test_finalize_chain_update() -> anyhow::Result<()> {
438+
struct TestCase<'a> {
439+
name: &'a str,
440+
/// Initial blockchain height to start the env with.
441+
initial_env_height: u32,
442+
/// Initial checkpoint heights to start with.
443+
initial_cps: &'a [u32],
444+
/// The final blockchain height of the env.
445+
final_env_height: u32,
446+
/// The anchors to test with: `(height, txid)`. Only the height is provided as we can fetch
447+
/// the blockhash from the env.
448+
anchors: &'a [(u32, Txid)],
449+
}
450+
451+
let test_cases = [
452+
TestCase {
453+
name: "chain_extends",
454+
initial_env_height: 60,
455+
initial_cps: &[59, 60],
456+
final_env_height: 90,
457+
anchors: &[],
458+
},
459+
TestCase {
460+
name: "introduce_older_heights",
461+
initial_env_height: 50,
462+
initial_cps: &[10, 15],
463+
final_env_height: 50,
464+
anchors: &[(11, h!("A")), (14, h!("B"))],
465+
},
466+
TestCase {
467+
name: "introduce_older_heights_after_chain_extends",
468+
initial_env_height: 50,
469+
initial_cps: &[10, 15],
470+
final_env_height: 100,
471+
anchors: &[(11, h!("A")), (14, h!("B"))],
472+
},
473+
];
474+
475+
for (i, t) in test_cases.into_iter().enumerate() {
476+
println!("[{}] running test case: {}", i, t.name);
477+
478+
let env = TestEnv::new()?;
479+
let base_url = format!("http://{}", &env.electrsd.esplora_url.clone().unwrap());
480+
let client = Builder::new(base_url.as_str()).build_async()?;
481+
482+
// set env to `initial_env_height`
483+
if let Some(to_mine) = t
484+
.initial_env_height
485+
.checked_sub(env.make_checkpoint_tip().height())
486+
{
487+
env.mine_blocks(to_mine as _, None)?;
488+
}
489+
while client.get_height().await? < t.initial_env_height {
490+
std::thread::sleep(Duration::from_millis(10));
491+
}
492+
493+
// craft initial `local_chain`
494+
let local_chain = {
495+
let (mut chain, _) = LocalChain::from_genesis_hash(env.genesis_hash()?);
496+
let chain_tip = chain.tip();
497+
let update_blocks = init_chain_update(&client, &chain_tip).await?;
498+
let update_anchors = t
499+
.initial_cps
500+
.iter()
501+
.map(|&height| -> anyhow::Result<_> {
502+
Ok((
503+
BlockId {
504+
height,
505+
hash: env.bitcoind.client.get_block_hash(height as _)?,
506+
},
507+
Txid::all_zeros(),
508+
))
509+
})
510+
.collect::<anyhow::Result<BTreeSet<_>>>()?;
511+
let chain_update =
512+
finalize_chain_update(&client, &chain_tip, &update_anchors, update_blocks)
513+
.await?;
514+
chain.apply_update(chain_update)?;
515+
chain
516+
};
517+
println!("local chain height: {}", local_chain.tip().height());
518+
519+
// extend env chain
520+
if let Some(to_mine) = t
521+
.final_env_height
522+
.checked_sub(env.make_checkpoint_tip().height())
523+
{
524+
env.mine_blocks(to_mine as _, None)?;
525+
}
526+
while client.get_height().await? < t.final_env_height {
527+
std::thread::sleep(Duration::from_millis(10));
528+
}
529+
530+
// craft update
531+
let update = {
532+
let local_tip = local_chain.tip();
533+
let update_blocks = init_chain_update(&client, &local_tip).await?;
534+
let update_anchors = t
535+
.anchors
536+
.iter()
537+
.map(|&(height, txid)| -> anyhow::Result<_> {
538+
Ok((
539+
BlockId {
540+
height,
541+
hash: env.bitcoind.client.get_block_hash(height as _)?,
542+
},
543+
txid,
544+
))
545+
})
546+
.collect::<anyhow::Result<_>>()?;
547+
finalize_chain_update(&client, &local_tip, &update_anchors, update_blocks).await?
548+
};
549+
550+
// apply update
551+
let mut updated_local_chain = local_chain.clone();
552+
updated_local_chain.apply_update(update)?;
553+
println!(
554+
"updated local chain height: {}",
555+
updated_local_chain.tip().height()
556+
);
557+
558+
assert!(
559+
{
560+
let initial_heights = local_chain
561+
.iter_checkpoints()
562+
.map(|cp| cp.height())
563+
.collect::<BTreeSet<_>>();
564+
let updated_heights = updated_local_chain
565+
.iter_checkpoints()
566+
.map(|cp| cp.height())
567+
.collect::<BTreeSet<_>>();
568+
updated_heights.is_superset(&initial_heights)
569+
},
570+
"heights from the initial chain must all be in the updated chain",
571+
);
572+
573+
assert!(
574+
{
575+
let exp_anchor_heights = t
576+
.anchors
577+
.iter()
578+
.map(|(h, _)| *h)
579+
.chain(t.initial_cps.iter().copied())
580+
.collect::<BTreeSet<_>>();
581+
let anchor_heights = updated_local_chain
582+
.iter_checkpoints()
583+
.map(|cp| cp.height())
584+
.collect::<BTreeSet<_>>();
585+
anchor_heights.is_superset(&exp_anchor_heights)
586+
},
587+
"anchor heights must all be in updated chain",
588+
);
589+
}
590+
591+
Ok(())
592+
}
593+
}

0 commit comments

Comments
 (0)