🏡 index : ~doyle/blocks.ls.git

use crate::database::{Connection, Result};
use serde::{Deserialize, Deserializer};
use serde::de::Error;
use tokio::time::Instant;
use tokio_postgres::types::Json;
use tokio_postgres::Row;

#[derive(Debug)]
pub struct Transaction {
    pub hash: Vec<u8>,
    pub version: i32,
    pub lock_time: i32,
    pub weight: i64,
    pub coinbase: bool,
    pub replace_by_fee: bool,
    pub inputs: Json<Vec<TransactionInput>>,
    pub outputs: Json<Vec<TransactionOutput>>,
}

impl Transaction {
    pub fn from_row(row: Row) -> Result<Self> {
        Ok(Self {
            hash: row.try_get("hash")?,
            version: row.try_get("version")?,
            lock_time: row.try_get("lock_time")?,
            weight: row.try_get("weight")?,
            coinbase: row.try_get("coinbase")?,
            replace_by_fee: row.try_get("replace_by_fee")?,
            inputs: row.try_get("inputs")?,
            outputs: row.try_get("outputs")?,
        })
    }
}

#[derive(Deserialize, Debug)]
pub struct TransactionInput {
    pub previous_output_tx: Option<TransactionOutput>,
    #[serde(deserialize_with = "trim_hex_prefix")]
    pub script: String,
}

#[derive(Deserialize, Debug)]
pub struct TransactionOutput {
    pub value: i64,
    #[serde(deserialize_with = "trim_hex_prefix")]
    pub script: String,
    pub unspendable: bool,
    pub address: Option<String>,
}

fn trim_hex_prefix<'de, D: Deserializer<'de>>(deserializer: D) -> std::result::Result<String, D::Error> {
    let mut s = String::deserialize(deserializer)?;
    s.remove(0);
    s.remove(0);
    Ok(s)
}

pub async fn fetch_transactions_for_block(db: &Connection, id: i64) -> Result<Vec<Transaction>> {
    let transactions = db
        .query(
            "SELECT
           transactions.*,
           JSON_AGG(transaction_inputs) AS inputs,
           JSON_AGG(transaction_outputs) AS outputs
         FROM transactions
         LEFT JOIN
           (
             SELECT
               row_to_json(transaction_outputs) AS previous_output_tx,
               transaction_inputs.*
             FROM transaction_inputs
             LEFT JOIN transaction_outputs
               ON transaction_outputs.id = transaction_inputs.previous_output
           ) transaction_inputs
           ON transactions.id = transaction_inputs.transaction_id
         LEFT JOIN transaction_outputs
	       ON transactions.id = transaction_outputs.transaction_id
	     WHERE transactions.block_id = $1
	     GROUP BY transactions.id
	     ORDER BY transactions.id ASC",
            &[&id],
        )
        .await?;

    transactions
        .into_iter()
        .map(Transaction::from_row)
        .collect()
}