// We avoid fetching blocks higher than previously fetched `latest_blocks` as the local chain
// tip is used to signal for the last-synced-up-to-height.
- let &tip_height = latest_blocks
- .keys()
- .last()
- .expect("must have atleast one entry");
- if height > tip_height {
- return Ok(None);
+ if let Some(tip_height) = latest_blocks.keys().last().copied() {
+ if height > tip_height {
+ return Ok(None);
+ }
}
Ok(Some(client.get_block_hash(height).await?))
anchors: &BTreeSet<(ConfirmationBlockTime, Txid)>,
) -> Result<CheckPoint, Error> {
let mut point_of_agreement = None;
+ let mut local_cp_hash = local_tip.hash();
let mut conflicts = vec![];
+
for local_cp in local_tip.iter() {
let remote_hash = match fetch_block(client, latest_blocks, local_cp.height()).await? {
Some(hash) => hash,
None => continue,
};
if remote_hash == local_cp.hash() {
- point_of_agreement = Some(local_cp.clone());
+ point_of_agreement = Some(local_cp);
break;
- } else {
- // it is not strictly necessary to include all the conflicted heights (we do need the
- // first one) but it seems prudent to make sure the updated chain's heights are a
- // superset of the existing chain after update.
- conflicts.push(BlockId {
- height: local_cp.height(),
- hash: remote_hash,
- });
}
+ local_cp_hash = local_cp.hash();
+ // It is not strictly necessary to include all the conflicted heights (we do need the
+ // first one) but it seems prudent to make sure the updated chain's heights are a
+ // superset of the existing chain after update.
+ conflicts.push(BlockId {
+ height: local_cp.height(),
+ hash: remote_hash,
+ });
}
- let mut tip = point_of_agreement.expect("remote esplora should have same genesis block");
+ let mut tip = match point_of_agreement {
+ Some(tip) => tip,
+ None => {
+ return Err(Box::new(esplora_client::Error::HeaderHashNotFound(
+ local_cp_hash,
+ )));
+ }
+ };
tip = tip
.extend(conflicts.into_iter().rev())
local_chain::LocalChain,
BlockId,
};
- use bdk_core::ConfirmationBlockTime;
+ use bdk_core::{bitcoin, ConfirmationBlockTime};
use bdk_testenv::{anyhow, bitcoincore_rpc::RpcApi, TestEnv};
use esplora_client::Builder;
}};
}
+ // Test that `chain_update` fails due to wrong network.
+ #[tokio::test]
+ async fn test_chain_update_wrong_network_error() -> anyhow::Result<()> {
+ let env = TestEnv::new()?;
+ let base_url = format!("http://{}", &env.electrsd.esplora_url.clone().unwrap());
+ let client = Builder::new(base_url.as_str()).build_async()?;
+ let initial_height = env.rpc_client().get_block_count()? as u32;
+
+ let mine_to = 16;
+ let _ = env.mine_blocks((mine_to - initial_height) as usize, None)?;
+ while client.get_height().await? < mine_to {
+ std::thread::sleep(Duration::from_millis(64));
+ }
+ let latest_blocks = fetch_latest_blocks(&client).await?;
+ assert!(!latest_blocks.is_empty());
+ assert_eq!(latest_blocks.keys().last(), Some(&mine_to));
+
+ let genesis_hash =
+ bitcoin::constants::genesis_block(bitcoin::Network::Testnet4).block_hash();
+ let cp = bdk_chain::CheckPoint::new(BlockId {
+ height: 0,
+ hash: genesis_hash,
+ });
+
+ let anchors = BTreeSet::new();
+ let res = chain_update(&client, &latest_blocks, &cp, &anchors).await;
+ use esplora_client::Error;
+ assert!(
+ matches!(*res.unwrap_err(), Error::HeaderHashNotFound(hash) if hash == genesis_hash),
+ "`chain_update` should error if it can't connect to the local CP",
+ );
+
+ Ok(())
+ }
+
/// Ensure that update does not remove heights (from original), and all anchor heights are
/// included.
#[tokio::test]
// We avoid fetching blocks higher than previously fetched `latest_blocks` as the local chain
// tip is used to signal for the last-synced-up-to-height.
- let &tip_height = latest_blocks
- .keys()
- .last()
- .expect("must have atleast one entry");
- if height > tip_height {
- return Ok(None);
+ if let Some(tip_height) = latest_blocks.keys().last().copied() {
+ if height > tip_height {
+ return Ok(None);
+ }
}
Ok(Some(client.get_block_hash(height)?))
anchors: &BTreeSet<(ConfirmationBlockTime, Txid)>,
) -> Result<CheckPoint, Error> {
let mut point_of_agreement = None;
+ let mut local_cp_hash = local_tip.hash();
let mut conflicts = vec![];
+
for local_cp in local_tip.iter() {
let remote_hash = match fetch_block(client, latest_blocks, local_cp.height())? {
Some(hash) => hash,
None => continue,
};
if remote_hash == local_cp.hash() {
- point_of_agreement = Some(local_cp.clone());
+ point_of_agreement = Some(local_cp);
break;
- } else {
- // it is not strictly necessary to include all the conflicted heights (we do need the
- // first one) but it seems prudent to make sure the updated chain's heights are a
- // superset of the existing chain after update.
- conflicts.push(BlockId {
- height: local_cp.height(),
- hash: remote_hash,
- });
}
+ local_cp_hash = local_cp.hash();
+ // It is not strictly necessary to include all the conflicted heights (we do need the
+ // first one) but it seems prudent to make sure the updated chain's heights are a
+ // superset of the existing chain after update.
+ conflicts.push(BlockId {
+ height: local_cp.height(),
+ hash: remote_hash,
+ });
}
- let mut tip = point_of_agreement.expect("remote esplora should have same genesis block");
+ let mut tip = match point_of_agreement {
+ Some(tip) => tip,
+ None => {
+ return Err(Box::new(esplora_client::Error::HeaderHashNotFound(
+ local_cp_hash,
+ )));
+ }
+ };
tip = tip
.extend(conflicts.into_iter().rev())
#[cfg(test)]
mod test {
use crate::blocking_ext::{chain_update, fetch_latest_blocks};
+ use bdk_chain::bitcoin;
use bdk_chain::bitcoin::hashes::Hash;
use bdk_chain::bitcoin::Txid;
use bdk_chain::local_chain::LocalChain;
}};
}
+ // Test that `chain_update` fails due to wrong network.
+ #[test]
+ fn test_chain_update_wrong_network_error() -> anyhow::Result<()> {
+ let env = TestEnv::new()?;
+ let base_url = format!("http://{}", &env.electrsd.esplora_url.clone().unwrap());
+ let client = Builder::new(base_url.as_str()).build_blocking();
+ let initial_height = env.rpc_client().get_block_count()? as u32;
+
+ let mine_to = 16;
+ let _ = env.mine_blocks((mine_to - initial_height) as usize, None)?;
+ while client.get_height()? < mine_to {
+ std::thread::sleep(Duration::from_millis(64));
+ }
+ let latest_blocks = fetch_latest_blocks(&client)?;
+ assert!(!latest_blocks.is_empty());
+ assert_eq!(latest_blocks.keys().last(), Some(&mine_to));
+
+ let genesis_hash =
+ bitcoin::constants::genesis_block(bitcoin::Network::Testnet4).block_hash();
+ let cp = bdk_chain::CheckPoint::new(BlockId {
+ height: 0,
+ hash: genesis_hash,
+ });
+
+ let anchors = BTreeSet::new();
+ let res = chain_update(&client, &latest_blocks, &cp, &anchors);
+ use esplora_client::Error;
+ assert!(
+ matches!(*res.unwrap_err(), Error::HeaderHashNotFound(hash) if hash == genesis_hash),
+ "`chain_update` should error if it can't connect to the local CP",
+ );
+
+ Ok(())
+ }
+
/// Ensure that update does not remove heights (from original), and all anchor heights are
/// included.
#[test]