🏡 index : ~doyle/blocks.ls.git

author Jordan Doyle <jordan@doyle.la> 2022-05-23 0:07:27.0 +00:00:00
committer Jordan Doyle <jordan@doyle.la> 2022-05-23 0:11:31.0 +00:00:00
commit
9dc3110ee521662ce5059dfe9f467622734be489 [patch]
tree
73fcf07631ffe3852644894167920f705e580cd0
parent
a87528a3f6ac6199a1a4c64ee1740b41111ccec6
download
9dc3110ee521662ce5059dfe9f467622734be489.tar.gz

Calculate fees, weight, block size & use locale for number display



Diff

 Cargo.lock                           | 37 +++++++++++++++++++++++++++-
 frontend/src/lib/Blocks.svelte       |  7 ++---
 frontend/src/lib/Transaction.svelte  |  4 +--
 frontend/src/lib/Transactions.svelte | 17 +++++++++---
 indexer/src/main.rs                  |  4 ++-
 web-api/Cargo.toml                   |  2 +-
 web-api/config.toml                  |  5 +----
 web-api/src/database/blocks.rs       |  9 ++++---
 web-api/src/database/transactions.rs | 51 ++++++++++++++++++++++++++++++-------
 web-api/src/main.rs                  |  1 +-
 web-api/src/methods/block.rs         | 15 ++++++++---
 web-api/src/methods/transaction.rs   | 28 +++++++++++++++-----
 web-api/src/middleware/logging.rs    |  1 +-
 13 files changed, 141 insertions(+), 40 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index b76a3e0..fc5806c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -21,6 +21,12 @@ dependencies = [
]

[[package]]
name = "arrayvec"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"

[[package]]
name = "async-trait"
version = "0.1.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -819,6 +825,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"

[[package]]
name = "postgres"
version = "0.19.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8bbcd5f6deb39585a0d9f4ef34c4a41c25b7ad26d23c75d837d78c8e7adc85f"
dependencies = [
 "bytes",
 "fallible-iterator",
 "futures",
 "log",
 "tokio",
 "tokio-postgres",
]

[[package]]
name = "postgres-protocol"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1006,6 +1026,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c31b5c4033f8fdde8700e4657be2c497e7288f01515be52168c631e2e4d4086"

[[package]]
name = "rust_decimal"
version = "1.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22dc69eadbf0ee2110b8d20418c0c6edbaefec2811c4963dc17b6344e11fe0f8"
dependencies = [
 "arrayvec",
 "byteorder",
 "bytes",
 "num-traits",
 "postgres",
 "serde",
 "tokio-postgres",
]

[[package]]
name = "ryu"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1582,11 +1617,13 @@ name = "web-api"
version = "0.1.0"
dependencies = [
 "axum",
 "bitcoin",
 "chrono",
 "clap",
 "deadpool-postgres",
 "futures",
 "hex 0.4.3",
 "rust_decimal",
 "serde",
 "tokio",
 "tokio-postgres",
diff --git a/frontend/src/lib/Blocks.svelte b/frontend/src/lib/Blocks.svelte
index dcbdd06..fc9712c 100644
--- a/frontend/src/lib/Blocks.svelte
+++ b/frontend/src/lib/Blocks.svelte
@@ -29,10 +29,9 @@
            </span>
          </td>
          <td>{block.mined_by?.pool || "Unknown"}</td>
          <td>{block.tx_count}</td>
          <td>{block.bits}</td>
          <!-- todo: this isn't really size -->
          <td>{block.weight}</td>
          <td>{block.tx_count.toLocaleString()}</td>
          <td>{(block.size / 1000).toLocaleString()}</td>
          <td>{(block.weight / 1000).toLocaleString()}</td>
        </tr>
      {/each}
    </tbody>
diff --git a/frontend/src/lib/Transaction.svelte b/frontend/src/lib/Transaction.svelte
index 9c5d6dd..efae15d 100644
--- a/frontend/src/lib/Transaction.svelte
+++ b/frontend/src/lib/Transaction.svelte
@@ -80,7 +80,7 @@

                {#if input.previous_output}
                  <div class="amount">
                    <code>{(input.previous_output.value / scale).toFixed(8)} BTC</code>
                    <code>{(input.previous_output.value / scale).toFixed(8).toLocaleString()} BTC</code>
                  </div>
                {/if}
              </div>
@@ -123,7 +123,7 @@
              </div>

              <div class="amount">
                <code>{(output.value / scale).toFixed(8)} BTC</code>
                <code>{(output.value / scale).toFixed(8).toLocaleString()} BTC</code>
              </div>
            </div>
          </div>
diff --git a/frontend/src/lib/Transactions.svelte b/frontend/src/lib/Transactions.svelte
index 134b2b7..2ed4519 100644
--- a/frontend/src/lib/Transactions.svelte
+++ b/frontend/src/lib/Transactions.svelte
@@ -2,6 +2,8 @@
  import { t as _ } from "$lib/i18n";

  export let transactions;

  const WITNESS_SCALE_FACTOR = 4;
</script>

<table>
@@ -18,9 +20,18 @@
    {#each transactions as txn}
      <tr>
        <th><a href={`/tx/${txn.hash}`}><code>{txn.hash}</code></a></th>
        <td>{txn.amount} BTC</td>
        <td>{txn.size} vB</td>
        <td>{txn.fee} sat/vB</td>
        <td><code>{(txn.output_total_value / Math.pow(10, 8)).toFixed(8)} BTC</code></td>
        <td><code>{Math.ceil((txn.weight + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR).toLocaleString()} vB</code></td>
        <td>
          <code>
            {#if txn.input_total_value > 0}
              {((txn.input_total_value - txn.output_total_value) / ((txn.weight + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR)).toFixed(2).toLocaleString()}
            {:else}
              0
            {/if}
            sat/vB
          </code>
        </td>
      </tr>
    {/each}
  </tbody>
diff --git a/indexer/src/main.rs b/indexer/src/main.rs
index 1c2c266..44507d1 100644
--- a/indexer/src/main.rs
+++ b/indexer/src/main.rs
@@ -46,7 +46,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let height = bitcoin_rpc.get_block_count().await?;
    eprintln!("Current block height: {}", height);

    let start = 737000;
    let start = args.start;

    let (tx, mut rx) = tokio::sync::mpsc::channel::<(u64, BlockHash, Block)>(200);

@@ -295,6 +295,8 @@ pub struct Args {
    pub verbose: usize,
    #[clap(short, long, parse(try_from_str = Config::from_toml_path))]
    pub config: Config,
    /// Block height to start at
    pub start: u64,
}

impl Args {
diff --git a/web-api/Cargo.toml b/web-api/Cargo.toml
index 614f203..0c49e13 100644
--- a/web-api/Cargo.toml
+++ b/web-api/Cargo.toml
@@ -7,7 +7,9 @@ edition = "2021"

[dependencies]
axum = "0.5"
bitcoin = "0.28"
deadpool-postgres = "0.10"
rust_decimal = { version = "1.23", features = ["db-tokio-postgres"] }
chrono = { version = "0.4", features = ["serde"] }
clap = { version = "3.1", features = ["derive"] }
futures = "0.3"
diff --git a/web-api/config.toml b/web-api/config.toml
deleted file mode 100644
index 4e6ed98..0000000
--- a/web-api/config.toml
+++ /dev/null
@@ -1,5 +0,0 @@
[database]
user = "postgres"
password = "postgres"
host = "127.0.0.1"
database = "postgres"
diff --git a/web-api/src/database/blocks.rs b/web-api/src/database/blocks.rs
index 04cd55b..1ff9f51 100644
--- a/web-api/src/database/blocks.rs
+++ b/web-api/src/database/blocks.rs
@@ -1,5 +1,5 @@
use crate::database::{Connection, Result};
use chrono::{DateTime, NaiveDateTime, Utc};
use chrono::NaiveDateTime;
use tokio_postgres::Row;

#[derive(Debug)]
@@ -42,17 +42,19 @@ pub async fn fetch_height(db: &Connection) -> Result<u64> {
}

pub type TransactionCount = i64;
pub type TransactionWeight = rust_decimal::Decimal;

pub async fn fetch_latest_blocks(
    db: &Connection,
    count: i64,
    offset: i64,
) -> Result<Vec<(Block, TransactionCount, Vec<u8>)>> {
) -> Result<Vec<(Block, TransactionCount, TransactionWeight, Vec<u8>)>> {
    let blocks = db
        .query(
            "SELECT
               blocks.*,
               COUNT(transactions.id) AS tx_count,
               SUM(transactions.weight) AS tx_weight,
               (
                 SELECT script
                 FROM transactions
@@ -76,8 +78,9 @@ pub async fn fetch_latest_blocks(
        .into_iter()
        .map(|row| {
            let tx_count = row.try_get("tx_count")?;
            let tx_weight = row.try_get("tx_weight")?;
            let coinbase_script = row.try_get("coinbase_script")?;
            Ok((Block::from_row(row)?, tx_count, coinbase_script))
            Ok((Block::from_row(row)?, tx_count, tx_weight, coinbase_script))
        })
        .collect::<Result<Vec<_>>>()
}
diff --git a/web-api/src/database/transactions.rs b/web-api/src/database/transactions.rs
index 9ac1466..fb1a6ba 100644
--- a/web-api/src/database/transactions.rs
+++ b/web-api/src/database/transactions.rs
@@ -1,17 +1,17 @@
use crate::database::{Connection, Result};
use futures::StreamExt;
use serde::de::Error;
use serde::{Deserialize, Deserializer};
use tokio::time::Instant;
use tokio_postgres::types::{Json, ToSql};
use tokio_postgres::Row;
use tokio_postgres::{
    types::{Json, ToSql},
    Row,
};

#[derive(Debug)]
pub struct Transaction {
    pub hash: Vec<u8>,
    pub version: i32,
    pub lock_time: i32,
    pub weight: i64,
    pub lock_time: i32,
    pub coinbase: bool,
    pub replace_by_fee: bool,
    pub inputs: Json<Vec<TransactionInput>>,
@@ -23,8 +23,8 @@ impl Transaction {
        Ok(Self {
            hash: row.try_get("hash")?,
            version: row.try_get("version")?,
            lock_time: row.try_get("lock_time")?,
            weight: row.try_get("weight")?,
            lock_time: row.try_get("lock_time")?,
            coinbase: row.try_get("coinbase")?,
            replace_by_fee: row.try_get("replace_by_fee")?,
            inputs: row.try_get("inputs")?,
@@ -199,9 +199,32 @@ pub async fn fetch_transactions_for_address(
        .collect()
}

pub async fn fetch_latest_transactions(db: &Connection, limit: i64) -> Result<Vec<Transaction>> {
pub struct TransactionWithDetails {
    pub input_total_value: rust_decimal::Decimal,
    pub output_total_value: rust_decimal::Decimal,
    pub transaction: Transaction,
}

pub async fn fetch_latest_transactions(
    db: &Connection,
    limit: i64,
) -> Result<Vec<TransactionWithDetails>> {
    let select_query = "
        SELECT *, JSON_BUILD_ARRAY() AS inputs, JSON_BUILD_ARRAY() AS outputs
        SELECT transactions.*,
            JSON_BUILD_ARRAY() AS inputs,
            JSON_BUILD_ARRAY() AS outputs,
            (
                SELECT SUM(po.value)
                FROM transaction_inputs input
                LEFT JOIN transaction_outputs po
                    ON po.id = input.previous_output
                WHERE input.transaction_id = transactions.id
            ) AS input_total_value,
            (
                SELECT SUM(out.value)
                FROM transaction_outputs out
                WHERE out.transaction_id = transactions.id
            ) AS output_total_value
        FROM transactions
        ORDER BY transactions.id DESC
        LIMIT $1
@@ -211,7 +234,17 @@ pub async fn fetch_latest_transactions(db: &Connection, limit: i64) -> Result<Ve

    transactions
        .into_iter()
        .map(Transaction::from_row)
        .map(|tx| {
            Ok(TransactionWithDetails {
                input_total_value: tx
                    .try_get::<_, Option<_>>("input_total_value")?
                    .unwrap_or_default(),
                output_total_value: tx
                    .try_get::<_, Option<_>>("output_total_value")?
                    .unwrap_or_default(),
                transaction: Transaction::from_row(tx)?,
            })
        })
        .collect()
}

diff --git a/web-api/src/main.rs b/web-api/src/main.rs
index 6f88655..9765fec 100644
--- a/web-api/src/main.rs
+++ b/web-api/src/main.rs
@@ -5,7 +5,6 @@ mod middleware;

use crate::config::Config;
use crate::database::Database;
use axum::routing::get;
use axum::{Extension, Router};
use clap::Parser;
use tower::ServiceBuilder;
diff --git a/web-api/src/methods/block.rs b/web-api/src/methods/block.rs
index d79fb69..776e234 100644
--- a/web-api/src/methods/block.rs
+++ b/web-api/src/methods/block.rs
@@ -1,8 +1,9 @@
use crate::Database;
use axum::extract::{Path, Query};
use axum::{Extension, Json};
use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR;
use bitcoin::VarInt;
use chrono::NaiveDateTime;
use futures::StreamExt;
use serde::{Deserialize, Serialize};

#[derive(Serialize)]
@@ -26,7 +27,9 @@ pub struct BlockList {
    bits: i32,
    nonce: u32,
    difficulty: i64,
    weight: u64,
    tx_count: i64,
    size: i32,
}

#[derive(Deserialize)]
@@ -57,7 +60,7 @@ pub async fn list(
    Json(
        blocks
            .into_iter()
            .map(|(mut block, tx_count, coinbase_script)| {
            .map(|(mut block, tx_count, tx_weight, coinbase_script)| {
                // TODO: do this on insert
                block.hash.reverse();

@@ -67,9 +70,13 @@ pub async fn list(
                    height: block.height,
                    version: block.version,
                    timestamp: block.timestamp,
                    size: block.size,
                    bits: block.bits,
                    nonce: block.nonce,
                    difficulty: block.difficulty,
                    weight: (u64::try_from(WITNESS_SCALE_FACTOR).unwrap()
                        * u64::try_from(VarInt(u64::try_from(tx_count).unwrap()).len()).unwrap())
                        + u64::try_from(tx_weight.mantissa()).unwrap(),
                    tx_count,
                }
            })
@@ -103,8 +110,8 @@ pub struct Block {
pub struct Transaction {
    pub hash: String,
    pub version: i32,
    pub lock_time: i32,
    pub weight: i64,
    pub lock_time: i32,
    pub coinbase: bool,
    pub replace_by_fee: bool,
    #[serde(skip_serializing_if = "Vec::is_empty")]
@@ -120,8 +127,8 @@ impl From<crate::database::transactions::Transaction> for Transaction {
        Transaction {
            hash: hex::encode(tx.hash),
            version: tx.version,
            lock_time: tx.lock_time,
            weight: tx.weight,
            lock_time: tx.lock_time,
            coinbase: tx.coinbase,
            replace_by_fee: tx.replace_by_fee,
            inputs: tx.inputs.0.into_iter().map(Into::into).collect(),
diff --git a/web-api/src/methods/transaction.rs b/web-api/src/methods/transaction.rs
index 4254588..55224b9 100644
--- a/web-api/src/methods/transaction.rs
+++ b/web-api/src/methods/transaction.rs
@@ -1,11 +1,8 @@
use crate::database::transactions::{fetch_latest_transactions, fetch_transaction_by_hash};
use crate::{
    database::transactions::fetch_transactions_for_address, methods::block::Transaction, Database,
};
use crate::{methods::block::Transaction, Database};
use axum::extract::Query;
use axum::{extract::Path, Extension, Json};
use futures::StreamExt;
use serde::Deserialize;
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
pub struct ListQuery {
@@ -13,10 +10,18 @@ pub struct ListQuery {
    limit: u32,
}

#[derive(Serialize)]
pub struct ListResponseTransaction {
    #[serde(flatten)]
    transaction: Transaction,
    input_total_value: i64,
    output_total_value: i64,
}

pub async fn list(
    Extension(database): Extension<Database>,
    Query(query): Query<ListQuery>,
) -> Json<Vec<Transaction>> {
) -> Json<Vec<ListResponseTransaction>> {
    let database = database.get().await.unwrap();

    let limit = std::cmp::min(20, std::cmp::max(5, query.limit));
@@ -25,7 +30,16 @@ pub async fn list(
        .await
        .unwrap();

    Json(transactions.into_iter().map(Into::into).collect())
    Json(
        transactions
            .into_iter()
            .map(|v| ListResponseTransaction {
                transaction: v.transaction.into(),
                input_total_value: i64::try_from(v.input_total_value.mantissa()).unwrap(),
                output_total_value: i64::try_from(v.output_total_value.mantissa()).unwrap(),
            })
            .collect(),
    )
}

pub async fn handle(
diff --git a/web-api/src/middleware/logging.rs b/web-api/src/middleware/logging.rs
index 688cf01..591e773 100644
--- a/web-api/src/middleware/logging.rs
+++ b/web-api/src/middleware/logging.rs
@@ -1,6 +1,5 @@
//! Logs each and every request out in a format similar to that of Apache's logs.

use axum::handler::Handler;
use axum::{
    extract::{self, FromRequest, RequestParts},
    http::{Request, Response},