electrs_client 0.1.12

A client for electrs
Documentation
use electrs_client::{BlockMeta, Client, Update, UpdateCapable};
use itertools::Itertools;
use nintypes::external::previewer::PreviewerBlockData;

type TestCache = PreviewerBlockData;
type TestFetched = Vec<PreviewerBlockData>;

async fn prepare() -> Client<TestCache> {
    dotenv::dotenv().ok();
    unsafe { std::env::set_var("EC_REORGS_PATH", "./test_cache") };
    let c = Client::new().await.unwrap();
    assert!(
        c.is_alive::<TestFetched>().await,
        "failed to connect to client"
    );
    c
}

#[tokio::test]
async fn vlad() {
    let client = prepare().await;
    let from = [
        client.get_electrs_block_meta(4655000).await.unwrap(),
        // client.get_last_electrs_block_meta().await.unwrap(),
    ];

    dbg!(&from);

    let updates = client
        .fetch_updates::<TestFetched>(&from)
        .await
        .inspect_err(|e| {
            dbg!(e);
        })
        .unwrap();

    let (new_blocks, reorgs) = updates.iter().fold((0, 0), |(inserts, reorgs), v| match v {
        electrs_client::Update::AddBlock { .. } => (inserts + 1, reorgs),
        electrs_client::Update::RemoveBlock { .. } => (inserts, reorgs + 1),
        electrs_client::Update::RemoveCachedBlock { .. } => (inserts, reorgs + 1),
    });

    println!("Applying new blocks reorgs: {reorgs} new_blocks: {new_blocks}");

    println!(
        "{:?}",
        updates
            .iter()
            .map(|v| {
                match v {
                    Update::AddBlock { height, hash, .. } => {
                        println!("{height}: {hash}");
                        height
                    }
                    Update::RemoveBlock { height } => {
                        println!("{height}: rm");
                        height
                    }
                    Update::RemoveCachedBlock { height, .. } => height,
                }
            })
            .take(30)
            .collect_vec()
    );

    updates.iter().for_each(|i| match i {
        Update::AddBlock {
            height,
            hash,
            block,
        } => {
            dbg!("{}, {}, {:?}", height, hash, block);
        }
        Update::RemoveBlock { height } => {
            panic!("{}", height);
        }
        Update::RemoveCachedBlock { height, block } => {
            panic!("{}, {:?}", height, block);
        }
    })
}

#[tokio::test]
async fn rest_hash_compare() {
    let c = prepare().await;

    // if error occured check `get_last_electrs_block_meta` because it converts endian
    let last = c.get_last_electrs_block_meta().await.unwrap();
    let last2 = c.get_electrs_block_meta(last.height).await.unwrap();
    assert!(last == last2);

    let prev = [c.get_electrs_block_meta(last.height - 1).await.unwrap()];

    let upd = c.fetch_updates::<TestFetched>(&prev).await.unwrap();

    let upd = upd
        .into_iter()
        .map(|v| match v {
            Update::AddBlock { height, hash, .. } => (height, hash),
            _ => panic!(),
        })
        .reduce(|acc, v| if v.0 == last.height { v } else { acc })
        .unwrap();
    assert!(last.block_hash == upd.1)
}

#[tokio::test]
async fn test_limit() {
    let mut c = prepare().await;
    c.config.limit = Some(5000);
    let blocks = c.fetch_updates::<TestFetched>(&[]).await.unwrap();
    assert_eq!(blocks.len(), 5000);

    c.config.limit = Some(3000);
    let blocks = c.fetch_updates::<TestFetched>(&[]).await.unwrap();
    assert_eq!(blocks.len(), 3000);
}

#[tokio::test]
async fn fetch_updates_without() {
    let c = prepare().await;
    let blocks = c.fetch_updates::<TestFetched>(&[]).await.unwrap();
    let mut metas = map_blocks(blocks);
    let mut start_heights = vec![];

    println!(
        "first fetch last 30 heights: {:?}",
        metas.iter().map(|v| v.height).collect_vec()
    );

    for i in 0..5 {
        let blocks = c.fetch_updates::<TestFetched>(&metas).await.unwrap();
        let heights = blocks
            .iter()
            .map(|v| match v {
                Update::AddBlock { height, .. } => height,
                _ => panic!("there isn't possible request to delete blocks"),
            })
            .collect_vec();

        // println!("{i} fetch first 30 heights: {:?}", heights.iter().take(30).collect_vec());
        // println!("{i} fetch last 30 heights: {:?}", heights.iter().rev().take(30).collect_vec());
        println!(
            "{i} fetch last 30 heights: {:?}",
            heights.iter().rev().take(1).collect_vec()
        );

        metas = map_blocks(blocks);
        start_heights.push(metas.first().map(|v| v.height).unwrap_or(0));
    }
    // ensure that start point always move forward
    start_heights.iter().reduce(|acc, v| {
        if acc >= v {
            panic!("start heights are not correct: {acc} {v}")
        }
        v
    });
}

fn map_blocks<T>(blocks: Vec<Update<T>>) -> Vec<BlockMeta> {
    let metas = blocks
        .iter()
        .rev()
        .take(31)
        .rev()
        .map(|v| match v {
            Update::AddBlock {
                height,
                hash,
                block,
            } => (height, hash, block),
            _ => panic!("there isn't possible request to delete blocks"),
        })
        .collect_vec();
    // NOTE: at this point i assume that electrs has at least 61 block which it able to give
    let f = metas[0];
    let mut prev_hash = f.1;
    metas
        .into_iter()
        .skip(1)
        .map(|v| {
            let a = BlockMeta {
                height: *v.0,
                block_hash: *v.1,
                prev_block_hash: *prev_hash,
            };
            prev_hash = v.1;
            a
        })
        .collect_vec()
}

#[tokio::test]
async fn fetch_updates_with_cachefiles() {
    let c = prepare().await;

    let mut start_heights = vec![];

    for i in 0..5 {
        let blocks = c.fetch_updates_from_cache::<TestFetched>().await.unwrap();
        let metas = blocks_to_metas(&blocks);

        for meta in metas.iter() {
            c.mark_as_processed(meta.height).await.unwrap();
        }

        println!(
            "{i} fetch first 30 heights: {:?}",
            metas.iter().take(30).map(|v| v.height).collect_vec()
        );
        println!(
            "{i} fetch last 30 heights: {:?}",
            metas
                .iter()
                .rev()
                .take(30)
                .rev()
                .map(|v| v.height)
                .collect_vec()
        );

        start_heights.push(metas.first().map(|v| v.height).unwrap_or(0));
    }

    println!(
        "start heights of fetch fetched heights: {:?}",
        start_heights.iter().collect_vec()
    );

    // ensure that start point always move forward
    start_heights.iter().reduce(|acc, v| {
        if acc >= v {
            panic!("start heights are not correct: {acc} {v}")
        }
        v
    });
}

fn blocks_to_metas<T: UpdateCapable>(blocks: &[Update<T>]) -> Vec<BlockMeta> {
    blocks
        .iter()
        .map(|v| match v {
            Update::AddBlock { block: b, .. } => BlockMeta {
                height: b.get_height(),
                block_hash: b.get_hash(),
                prev_block_hash: b.get_prev_hash(),
            },
            _ => panic!("there isn't possible request to delete blocks"),
        })
        .collect_vec()
}