🏡 index : ~doyle/blocks.ls.git

use std::sync::Arc;

use base64::Engine;
use bitcoin::{Block, BlockHash};
use reqwest::{
    header::{HeaderMap, AUTHORIZATION},
    Client,
};
use serde::Deserialize;
use serde_json::json;

#[derive(Clone)]
pub struct BitcoinRpc {
    client: Arc<Client>,
    url: Arc<str>,
}

impl BitcoinRpc {
    pub fn new(config: &crate::config::BitcoinRpc) -> Self {
        let client = Arc::new(
            reqwest::ClientBuilder::new()
                .default_headers({
                    let mut headers = HeaderMap::new();
                    headers.insert(
                        AUTHORIZATION,
                        format!(
                            "Basic {}",
                            base64::engine::general_purpose::STANDARD
                                .encode(format!("{}:{}", config.username, config.password))
                        )
                        .parse()
                        .unwrap(),
                    );
                    headers
                })
                .build()
                .unwrap(),
        );

        Self {
            client,
            url: Arc::from(format!("http://{}", config.address)),
        }
    }

    pub async fn get_block_height(&self) -> u64 {
        self.client
            .post(&*self.url)
            .json(&json!({
                "jsonrpc": "1.0",
                "id": 0,
                "method": "getblockcount",
                "params": []
            }))
            .send()
            .await
            .unwrap()
            .json::<RpcResult<u64>>()
            .await
            .unwrap()
            .result
    }

    pub async fn get_block_hash(&self, height: u64) -> BlockHash {
        self.client
            .post(&*self.url)
            .json(&json!({
                "jsonrpc": "1.0",
                "id": 0,
                "method": "getblockhash",
                "params": [height],
            }))
            .send()
            .await
            .unwrap()
            .json::<RpcResult<BlockHash>>()
            .await
            .unwrap()
            .result
    }

    pub async fn get_block(&self, hash: &BlockHash) -> Block {
        let hash = hash.to_string();

        let res = self
            .client
            .post(&*self.url)
            .json(&json!({
                "jsonrpc": "1.0",
                "id": 0,
                "method": "getblock",
                "params": [hash, 0],
            }))
            .send()
            .await
            .unwrap()
            .json::<RpcResult<String>>()
            .await
            .unwrap()
            .result;

        let bytes: Vec<u8> = bitcoin::hashes::hex::FromHex::from_hex(&res).unwrap();

        bitcoin::consensus::encode::deserialize(&bytes).unwrap()
    }
}

#[derive(Deserialize)]
#[allow(dead_code)]
pub struct RpcResult<T> {
    result: T,
    error: Option<String>,
    id: u64,
}