electrs_client 0.2.9

A client for electrs
Documentation
use std::collections::VecDeque;

use crate::{
    cache::UpdateCapable, errors::ClientResult, BlockHeight, ClientError, HasBlockInfo, MoreInfo,
};

use super::Client;

mod structs;
use itertools::Itertools;
pub use structs::*;
pub use upd_with_cache::REORG_CACHE_SIZE;

mod get_block_info;
mod upd_with_cache;

// (╥﹏╥)
impl<T: HasBlockInfo> Client<T> {
    /// preferred function to fetch updates if you want to handle state by yourself
    /// if you don't want to handle it by yourself take a look to `fetch_updates` with cache
    ///
    /// requirements to provided `last_blocks_smetas`:
    /// - heights should be sorted in asc order
    /// - heights should not containg gaps (e.g. 1,2,4,5,6 (here 3 is skipped))
    /// - hash chain should be valid (prev hash shoul be equal hash of previous block)
    ///
    ///  if reorg occures (hashes of fetched and provided are not equal)
    ///  returns sequence of `Update::RemoveBlock` which should be handled
    pub async fn fetch_updates(
        &self,
        last_blocks_metas: &[BlockMeta],
    ) -> ClientResult<Vec<Update<T>>> {
        if !last_blocks_metas.is_sorted_by(|a, b| a.height + 1 == b.height) {
            println!(
                "you give: {:?}",
                last_blocks_metas.iter().map(|v| v.height).collect_vec()
            );
            return Err(ClientError::NotSorted);
        }

        let last_block = last_blocks_metas
            .last()
            .cloned()
            .unwrap_or(BlockMeta::default());
        let start_height = last_block.height + 1;
        let fetched_blocks = self
            .fetch_range(start_height..=BlockHeight::MAX)
            .await
            .err_log()?;

        // perform checks of fetched
        if let Some(f) = fetched_blocks.first() {
            fetched_blocks
                .iter()
                .skip(1)
                .try_fold((f.block.height, f.block.hash), |s, v| {
                    let (p_height, p_hash) = s;
                    let (height, hash, prev) =
                        (v.block.height, v.block.hash, v.block.prev_block_hash);
                    if p_height + 1 != height {
                        println!("gap in {p_height} {height}",);
                        return Err(ClientError::ReceivedHeightContainsGaps);
                    }
                    if p_hash != prev {
                        return Err(ClientError::ReceivedHashChainInvalid);
                    }
                    Ok((height, hash))
                })?;
        };

        let valid_prev_hash = match fetched_blocks.first() {
            Some(v) => v.block.prev_block_hash,
            None => return Ok(vec![]),
        };

        let mut result = vec![];

        // if reorg occured
        if valid_prev_hash != last_block.hash && last_block.height != 0 {
            let reorgs_len = last_blocks_metas.len() as BlockHeight;
            let fetched_blocks = self
                .fetch_range(last_block.height + 1 - reorgs_len..=last_block.height)
                .await
                .err_log()?;
            let mut add = VecDeque::new();
            for (old, new) in last_blocks_metas.iter().zip(fetched_blocks).rev() {
                if old.hash != new.block.hash {
                    result.push(Update::RemoveBlock::<T> {
                        height: new.block.height,
                        block: None,
                    });
                    add.push_front(Update::AddBlock(new))
                } else {
                    break;
                }
            }

            result.extend(add);
        }

        result.extend(fetched_blocks.into_iter().map(|x| Update::AddBlock(x)));

        if result.is_empty() {
            eprintln!("Take your meds shizo");
            return Ok(result);
        }

        // integrity check for updates
        {
            let mut h = result.first().unwrap().get_height() - 1;
            if result.first().unwrap().is_remove() {
                h += 1;
            }

            for update in &result {
                match update {
                    Update::AddBlock(UpdateCapable {
                        block: BlockMeta { height, .. },
                        ..
                    }) => {
                        h += 1;

                        if h != *height {
                            println!(
                                "blocks provider return not valid block range: {:?}",
                                result
                                    .iter()
                                    .map(|x| {
                                        format!(
                                            "{}{}",
                                            if x.is_remove() { "r" } else { "a" },
                                            x.get_height()
                                        )
                                    })
                                    .collect_vec()
                            );

                            return Err(ClientError::CacheWithGapsFromProvider(h));
                        }
                    }
                    Update::RemoveBlock { .. } => {
                        h -= 1;
                    }
                }
            }
        }

        Ok(result)
    }
}