rostrum 14.0.1

An efficient implementation of Electrum Server with token support
Documentation
use std::sync::Arc;

use crate::chaindef::{BlockHash, ScriptHash};
use crate::edition::Edition;
use crate::errors::rpc_not_found;
use crate::mempool::{Tracker, MEMPOOL_HEIGHT};
use crate::query::balance::{
    balance_at_tip, historical_confirmed_balance, historical_unconfirmed_balance,
};
use crate::query::queryfilter::QueryFilter;
use crate::query::status::{
    by_block_height, scripthash_history, scripthash_listunspent, unconfirmed_history,
};
use crate::query::volume::historical_volume;
use crate::query::Query;
use crate::rpc::encoding::blockhash_to_hex;
use crate::store::DBStore;
use anyhow::Result;
use bitcoin_hashes::hex::ToHex;
use bitcoin_hashes::Hash;
use rayon::slice::ParallelSliceMut;
use serde_json::Value;

pub async fn get_balance(
    query: &Query,
    scripthash: ScriptHash,
    filter: &QueryFilter,
) -> Result<Value> {
    if !filter.has_height_filter() {
        // we can use the faster utxo index
        let (confirmed, _ctokens, unconfirmed, _utokens) = balance_at_tip(
            query.confirmed_index(),
            query.unconfirmed_index().index(),
            &scripthash,
            filter,
            &None,
        )
        .await?;

        return Ok(json!({
            "confirmed": confirmed,
            "unconfirmed": unconfirmed,
        }));
    }

    // Use the slower "full history search"

    let confirmed = query.confirmed_index().clone();
    let (confirmed, confirmed_outputs) =
        historical_confirmed_balance(&confirmed, scripthash, filter).await?;

    let unconfirmed = query.unconfirmed_index().index().clone();
    let unconfirmed =
        historical_unconfirmed_balance(&unconfirmed, confirmed_outputs, scripthash, filter).await?;

    Ok(json!({
        "confirmed": confirmed,
        "unconfirmed": unconfirmed,
    }))
}

pub async fn get_first_use(
    query: &Query,
    scripthash: ScriptHash,
    filter: &QueryFilter,
    edition: Edition,
) -> Result<Value> {
    let firstuse = query.scripthash_first_use(scripthash, filter).await?;

    if firstuse.is_none() {
        if edition == Edition::Edition2027Pre {
            return Ok(Value::Null);
        }
        return Err(rpc_not_found(format!(
            "scripthash '{}' not found",
            scripthash
        )));
    }
    let firstuse = firstuse.unwrap();

    let blockhash = if firstuse.0 == MEMPOOL_HEIGHT {
        BlockHash::all_zeros()
    } else {
        let h = query.get_headers(&[firstuse.0 as usize]).await;
        if h.is_empty() {
            warn!("expected to find header for height {}", firstuse.0);
            BlockHash::all_zeros()
        } else {
            h[0].block_hash()
        }
    };

    let height = if firstuse.0 == MEMPOOL_HEIGHT {
        0
    } else {
        firstuse.0
    };

    Ok(json!({
            "block_hash": blockhash_to_hex(&blockhash),
            "height": height,
            "block_height": height, // deprecated
            "tx_hash": firstuse.1.to_hex()
    }))
}

pub async fn get_first_spend(
    query: &Query,
    scripthash: ScriptHash,
    filter: &QueryFilter,
) -> Result<Value> {
    let Some((height, txid)) = query.scripthash_first_spend(scripthash, filter).await? else {
        return Ok(Value::Null);
    };

    let blockhash = if height == MEMPOOL_HEIGHT {
        BlockHash::all_zeros()
    } else {
        let h = query.get_headers(&[height as usize]).await;
        if h.is_empty() {
            warn!("expected to find header for height {}", height);
            BlockHash::all_zeros()
        } else {
            h[0].block_hash()
        }
    };

    let result_height = if height == MEMPOOL_HEIGHT { 0 } else { height };

    Ok(json!({
        "block_hash": blockhash_to_hex(&blockhash),
        "height": result_height,
        "block_height": result_height, // deprecated
        "tx_hash": txid.to_hex()
    }))
}

pub async fn get_history(
    store: &Arc<DBStore>,
    mempool: &Tracker,
    scripthash: ScriptHash,
    filter: QueryFilter,
) -> Result<Value> {
    Ok(json!(
        scripthash_history(store, mempool, scripthash, filter, None).await?
    ))
}

pub(crate) async fn get_mempool(
    query: &Query,
    scripthash: ScriptHash,
    filter: QueryFilter,
) -> Result<Value> {
    let mut history =
        unconfirmed_history(query.unconfirmed_index(), scripthash, filter, None).await?;
    history.par_sort_by(by_block_height);

    filter.filter_limit_offset(&mut history);

    Ok(json!(history))
}

pub async fn listunspent(
    query: &Query,
    scripthash: ScriptHash,
    filter: QueryFilter,
) -> Result<Value> {
    Ok(json!(
        scripthash_listunspent(
            query.confirmed_index(),
            query.unconfirmed_index().index(),
            scripthash,
            filter,
            None,
        )
        .await?
    ))
}

pub async fn get_volume(
    query: &Query,
    scripthash: ScriptHash,
    filter: &QueryFilter,
) -> Result<Value> {
    Ok(json!(
        historical_volume(
            query.confirmed_index(),
            query.unconfirmed_index().index(),
            scripthash,
            filter
        )
        .await?
    ))
}