Skip to content

Commit

Permalink
Embellish block page (ordinals#605)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey authored Oct 4, 2022
1 parent c89e539 commit 74ce5bb
Show file tree
Hide file tree
Showing 13 changed files with 294 additions and 72 deletions.
22 changes: 20 additions & 2 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use {
super::*,
bitcoin::consensus::encode::{deserialize, serialize},
bitcoin::BlockHeader,
bitcoincore_rpc::{Auth, Client, RpcApi},
bitcoincore_rpc::{json::GetBlockHeaderResult, Auth, Client, RpcApi},
rayon::iter::{IntoParallelRefIterator, ParallelIterator},
redb::WriteStrategy,
std::sync::atomic::{AtomicBool, Ordering},
Expand All @@ -21,6 +21,8 @@ pub(crate) struct Index {
database_path: PathBuf,
height_limit: Option<u64>,
reorged: AtomicBool,
genesis_block_coinbase_txid: Txid,
genesis_block_coinbase_transaction: Transaction,
}

#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -103,12 +105,20 @@ impl Index {

tx.commit()?;

let genesis_block_coinbase_transaction =
bitcoin::blockdata::constants::genesis_block(options.chain.network())
.coinbase()
.unwrap()
.clone();

Ok(Self {
client,
database,
database_path,
height_limit: options.height_limit,
reorged: AtomicBool::new(false),
genesis_block_coinbase_txid: genesis_block_coinbase_transaction.txid(),
genesis_block_coinbase_transaction,
})
}

Expand Down Expand Up @@ -427,12 +437,20 @@ impl Index {
self.client.get_block_header(&hash).into_option()
}

pub(crate) fn block_header_info(&self, hash: BlockHash) -> Result<Option<GetBlockHeaderResult>> {
self.client.get_block_header_info(&hash).into_option()
}

pub(crate) fn block_with_hash(&self, hash: BlockHash) -> Result<Option<Block>> {
self.client.get_block(&hash).into_option()
}

pub(crate) fn transaction(&self, txid: Txid) -> Result<Option<Transaction>> {
self.client.get_raw_transaction(&txid, None).into_option()
if txid == self.genesis_block_coinbase_txid {
Ok(Some(self.genesis_block_coinbase_transaction.clone()))
} else {
self.client.get_raw_transaction(&txid, None).into_option()
}
}

pub(crate) fn is_transaction_in_active_chain(&self, txid: Txid) -> Result<bool> {
Expand Down
4 changes: 3 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use {
rarity::Rarity,
sat_point::SatPoint,
subcommand::Subcommand,
tally::Tally,
},
anyhow::{anyhow, bail, Context, Error},
axum::{extract, http::StatusCode, response::Html, response::IntoResponse, routing::get, Router},
Expand All @@ -23,7 +24,7 @@ use {
consensus::{Decodable, Encodable},
hash_types::BlockHash,
hashes::Hash,
Address, Block, Network, OutPoint, Transaction, Txid,
Address, Block, Network, OutPoint, Transaction, TxOut, Txid,
},
chrono::{DateTime, NaiveDateTime, Utc},
clap::Parser,
Expand Down Expand Up @@ -71,6 +72,7 @@ mod ordinal;
mod rarity;
mod sat_point;
mod subcommand;
mod tally;

type Result<T = (), E = Error> = std::result::Result<T, E>;

Expand Down
112 changes: 96 additions & 16 deletions src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use {
deserialize_ordinal_from_str::DeserializeOrdinalFromStr,
templates::{
block::BlockHtml, clock::ClockSvg, home::HomeHtml, ordinal::OrdinalHtml, output::OutputHtml,
range::RangeHtml, transaction::TransactionHtml, Content,
range::RangeHtml, transaction::TransactionHtml, Content, PageHtml,
},
},
axum::{
Expand All @@ -30,6 +30,31 @@ use {
mod deserialize_ordinal_from_str;
mod templates;

enum ServerError {
InternalError(Error),
NotFound(String),
}

impl IntoResponse for ServerError {
fn into_response(self) -> Response {
match self {
Self::InternalError(error) => {
eprintln!("error serving request: {error}");
(
StatusCode::INTERNAL_SERVER_ERROR,
Html(
StatusCode::INTERNAL_SERVER_ERROR
.canonical_reason()
.unwrap_or_default(),
),
)
.into_response()
}
Self::NotFound(message) => (StatusCode::NOT_FOUND, Html(message)).into_response(),
}
}
}

fn html_status(status_code: StatusCode) -> (StatusCode, Html<&'static str>) {
(
status_code,
Expand Down Expand Up @@ -273,15 +298,31 @@ impl Server {
async fn output(
index: extract::Extension<Arc<Index>>,
extract::Path(outpoint): extract::Path<OutPoint>,
) -> impl IntoResponse {
match index.list(outpoint) {
Ok(Some(list)) => OutputHtml { outpoint, list }.page().into_response(),
Ok(None) => (StatusCode::NOT_FOUND, Html("Output unknown.".to_string())).into_response(),
Err(err) => {
eprintln!("Error serving request for output: {err}");
html_status(StatusCode::INTERNAL_SERVER_ERROR).into_response()
network: extract::Extension<Network>,
) -> Result<PageHtml, ServerError> {
let list = index
.list(outpoint)
.map_err(ServerError::InternalError)?
.ok_or_else(|| ServerError::NotFound(format!("Output {outpoint} unknown")))?;

let output = index
.transaction(outpoint.txid)
.map_err(ServerError::InternalError)?
.ok_or_else(|| ServerError::NotFound(format!("Output {outpoint} unknown")))?
.output
.into_iter()
.nth(outpoint.vout as usize)
.ok_or_else(|| ServerError::NotFound(format!("Output {outpoint} unknown")))?;

Ok(
OutputHtml {
outpoint,
list,
network: network.0,
output,
}
}
.page(),
)
}

async fn range(
Expand Down Expand Up @@ -314,8 +355,39 @@ impl Server {
extract::Path(hash): extract::Path<BlockHash>,
index: extract::Extension<Arc<Index>>,
) -> impl IntoResponse {
let info = match index.block_header_info(hash) {
Ok(Some(info)) => info,
Ok(None) => {
return (
StatusCode::NOT_FOUND,
Html(
StatusCode::NOT_FOUND
.canonical_reason()
.unwrap_or_default()
.to_string(),
),
)
.into_response()
}
Err(error) => {
eprintln!("Error serving request for block with hash {hash}: {error}");
return (
StatusCode::INTERNAL_SERVER_ERROR,
Html(
StatusCode::INTERNAL_SERVER_ERROR
.canonical_reason()
.unwrap_or_default()
.to_string(),
),
)
.into_response();
}
};

match index.block_with_hash(hash) {
Ok(Some(block)) => BlockHtml::new(block).page().into_response(),
Ok(Some(block)) => BlockHtml::new(block, Height(info.height as u64))
.page()
.into_response(),
Ok(None) => (
StatusCode::NOT_FOUND,
Html(
Expand Down Expand Up @@ -859,7 +931,7 @@ mod tests {
StatusCode::OK,
r".*<title>Ordinal range 0–1</title>.*<h1>Ordinal range 0–1</h1>
<dl>
<dt>size</dt><dd>1</dd>
<dt>value</dt><dd>1</dd>
<dt>first</dt><dd><a href=/ordinal/0 class=mythic>0</a></dd>
</dl>.*",
);
Expand Down Expand Up @@ -931,9 +1003,10 @@ mod tests {
StatusCode::OK,
".*<title>Output 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0</title>.*<h1>Output 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0</h1>
<dl>
<dt>size</dt><dd>5000000000</dd>
<dt>value</dt><dd>5000000000</dd>
<dt>script pubkey</dt><dd>OP_PUSHBYTES_65 04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f OP_CHECKSIG</dd>
</dl>
<h2>Ordinal Ranges</h2>
<h2>1 Ordinal Range</h2>
<ul class=monospace>
<li><a href=/range/0/5000000000 class=mythic>0–5000000000</a></li>
</ul>.*",
Expand All @@ -945,7 +1018,7 @@ mod tests {
TestServer::new().assert_response(
"/output/0000000000000000000000000000000000000000000000000000000000000000:0",
StatusCode::NOT_FOUND,
"Output unknown.",
"Output 0000000000000000000000000000000000000000000000000000000000000000:0 unknown",
);
}

Expand Down Expand Up @@ -1064,7 +1137,14 @@ mod tests {
&format!("/block/{block_hash}"),
StatusCode::OK,
".*<h1>Block [[:xdigit:]]{64}</h1>
<h2>Transactions</h2>
<dl>
<dt>height</dt><dd>2</dd>
<dt>timestamp</dt><dd>0</dd>
<dt>size</dt><dd>203</dd>
<dt>weight</dt><dd>812</dd>
<dt>prev blockhash</dt><dd><a href=/block/659f9b67fbc0b5cba0ef6ebc0aea322e1c246e29e43210bd581f5f3bd36d17bf>659f9b67fbc0b5cba0ef6ebc0aea322e1c246e29e43210bd581f5f3bd36d17bf</a></dd>
</dl>
<h2>2 Transactions</h2>
<ul class=monospace>
<li><a href=/tx/[[:xdigit:]]{64}>[[:xdigit:]]{64}</a></li>
<li><a href=/tx/[[:xdigit:]]{64}>[[:xdigit:]]{64}</a></li>
Expand All @@ -1084,7 +1164,7 @@ mod tests {
StatusCode::OK,
&format!(
".*<title>Transaction {txid}</title>.*<h1>Transaction {txid}</h1>
<h2>Outputs</h2>
<h2>1 Output</h2>
<ul class=monospace>
<li>
<a href=/output/9068a11b8769174363376b606af9a4b8b29dd7b13d013f4b0cbbd457db3c3ce5:0>
Expand Down
19 changes: 14 additions & 5 deletions src/subcommand/server/templates/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ use super::*;
#[derive(Boilerplate)]
pub(crate) struct BlockHtml {
hash: BlockHash,
txids: Vec<Txid>,
block: Block,
height: Height,
}

impl BlockHtml {
pub(crate) fn new(block: Block) -> Self {
pub(crate) fn new(block: Block, height: Height) -> Self {
Self {
hash: block.header.block_hash(),
txids: block.txdata.iter().map(Transaction::txid).collect(),
block,
height,
}
}
}
Expand All @@ -30,11 +32,18 @@ mod tests {
assert_eq!(
BlockHtml::new(bitcoin::blockdata::constants::genesis_block(
Network::Bitcoin
))
), Height(0))
.to_string(),
"
<h1>Block 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f</h1>
<h2>Transactions</h2>
<dl>
<dt>height</dt><dd>0</dd>
<dt>timestamp</dt><dd>1231006505</dd>
<dt>size</dt><dd>285</dd>
<dt>weight</dt><dd>1140</dd>
<dt>prev blockhash</dt><dd><a href=/block/0000000000000000000000000000000000000000000000000000000000000000>0000000000000000000000000000000000000000000000000000000000000000</a></dd>
</dl>
<h2>1 Transaction</h2>
<ul class=monospace>
<li><a href=/tx/4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b>4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b</a></li>
</ul>
Expand Down
31 changes: 27 additions & 4 deletions src/subcommand/server/templates/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use super::*;
pub(crate) struct OutputHtml {
pub(crate) outpoint: OutPoint,
pub(crate) list: List,
pub(crate) network: Network,
pub(crate) output: TxOut,
}

impl Content for OutputHtml {
Expand All @@ -14,7 +16,12 @@ impl Content for OutputHtml {

#[cfg(test)]
mod tests {
use {super::*, pretty_assertions::assert_eq, unindent::Unindent};
use {
super::*,
bitcoin::{blockdata::script, PubkeyHash, Script},
pretty_assertions::assert_eq,
unindent::Unindent,
};

#[test]
fn unspent_output() {
Expand All @@ -23,15 +30,22 @@ mod tests {
outpoint: "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0"
.parse()
.unwrap(),
list: List::Unspent(vec![(0, 1), (1, 3)])
list: List::Unspent(vec![(0, 1), (1, 3)]),
network: Network::Bitcoin,
output: TxOut {
value: 3,
script_pubkey: Script::new_p2pkh(&PubkeyHash::all_zeros()),
},
}
.to_string(),
"
<h1>Output 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0</h1>
<dl>
<dt>size</dt><dd>3</dd>
<dt>value</dt><dd>3</dd>
<dt>script pubkey</dt><dd>OP_DUP OP_HASH160 OP_PUSHBYTES_20 0000000000000000000000000000000000000000 OP_EQUALVERIFY OP_CHECKSIG</dd>
<dt>address</dt><dd>1111111111111111111114oLvT2</dd>
</dl>
<h2>Ordinal Ranges</h2>
<h2>2 Ordinal Ranges</h2>
<ul class=monospace>
<li><a href=/ordinal/0 class=mythic>0</a></li>
<li><a href=/range/1/3 class=common>1–3</a></li>
Expand All @@ -49,10 +63,19 @@ mod tests {
.parse()
.unwrap(),
list: List::Spent,
network: Network::Bitcoin,
output: TxOut {
value: 1,
script_pubkey: script::Builder::new().push_scriptint(0).into_script(),
},
}
.to_string(),
"
<h1>Output 0000000000000000000000000000000000000000000000000000000000000000:0</h1>
<dl>
<dt>value</dt><dd>1</dd>
<dt>script pubkey</dt><dd>OP_0</dd>
</dl>
<p>Output has been spent.</p>
"
.unindent()
Expand Down
4 changes: 2 additions & 2 deletions src/subcommand/server/templates/range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ mod tests {
"
<h1>Ordinal range 0–1</h1>
<dl>
<dt>size</dt><dd>1</dd>
<dt>value</dt><dd>1</dd>
<dt>first</dt><dd><a href=/ordinal/0 class=mythic>0</a></dd>
</dl>
"
Expand All @@ -46,7 +46,7 @@ mod tests {
"
<h1>Ordinal range 1–10</h1>
<dl>
<dt>size</dt><dd>9</dd>
<dt>value</dt><dd>9</dd>
<dt>first</dt><dd><a href=/ordinal/1 class=common>1</a></dd>
</dl>
"
Expand Down
Loading

0 comments on commit 74ce5bb

Please sign in to comment.