Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add /input page #639

Merged
merged 2 commits into from
Oct 11, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ derive_more = "0.99.17"
dirs = "4.0.0"
env_logger = "0.9.0"
futures = "0.3.21"
hex = "0.4.3"
html-escaper = "0.2.0"
http = "0.2.6"
lazy_static = "1.4.0"
Expand Down
6 changes: 3 additions & 3 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ impl Index {

let mut errors = 0;
let block = loop {
match self.block_at_height(height) {
match self.block(height) {
Err(err) => {
if cfg!(test) {
return Err(err);
Expand Down Expand Up @@ -488,7 +488,7 @@ impl Index {
Ok(())
}

fn block_at_height(&self, height: u64) -> Result<Option<Block>> {
pub(crate) fn block(&self, height: u64) -> Result<Option<Block>> {
Ok(
self
.client
Expand Down Expand Up @@ -599,7 +599,7 @@ impl Index {
pub(crate) fn blocktime(&self, height: Height) -> Result<Blocktime> {
let height = height.n();

match self.block_at_height(height)? {
match self.block(height)? {
Some(block) => Ok(Blocktime::Confirmed(block.header.time.into())),
None => {
let tx = self.database.begin_read()?;
Expand Down
5 changes: 3 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ use {
axum_server::Handle,
bitcoin::{
blockdata::constants::COIN_VALUE,
consensus::{Decodable, Encodable},
consensus::{self, Decodable, Encodable},
hash_types::BlockHash,
hashes::Hash,
Address, Block, Network, OutPoint, Transaction, TxOut, Txid,
Address, Block, Network, OutPoint, Sequence, Transaction, TxIn, TxOut, Txid,
},
chrono::{DateTime, NaiveDateTime, Utc},
clap::Parser,
derive_more::{Display, FromStr},
lazy_static::lazy_static,
redb::{Database, ReadableTable, Table, TableDefinition, WriteTransaction},
regex::Regex,
serde::{Deserialize, Serialize},
Expand Down
79 changes: 60 additions & 19 deletions src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@ use super::*;

use {
self::{
deserialize_ordinal_from_str::DeserializeOrdinalFromStr,
deserialize_ordinal_from_str::DeserializeFromStr,
templates::{
block::BlockHtml, clock::ClockSvg, home::HomeHtml, ordinal::OrdinalHtml, output::OutputHtml,
range::RangeHtml, rare::RareTxt, transaction::TransactionHtml, Content, PageHtml,
BlockHtml, ClockSvg, Content, HomeHtml, InputHtml, OrdinalHtml, OutputHtml, PageHtml,
RangeHtml, RareTxt, TransactionHtml,
},
},
axum::{
body,
http::header,
response::{Redirect, Response},
},
lazy_static::lazy_static,
rust_embed::RustEmbed,
rustls_acme::{
acme::{LETS_ENCRYPT_PRODUCTION_DIRECTORY, LETS_ENCRYPT_STAGING_DIRECTORY},
Expand All @@ -22,8 +21,7 @@ use {
AcmeConfig,
},
serde::{de, Deserializer},
std::cmp::Ordering,
std::str,
std::{cmp::Ordering, str},
tokio_stream::StreamExt,
};

Expand Down Expand Up @@ -141,6 +139,7 @@ impl Server {
.route("/faq", get(Self::faq))
.route("/favicon.ico", get(Self::favicon))
.route("/height", get(Self::height))
.route("/input/:block/:transaction/:input", get(Self::input))
.route("/ordinal/:ordinal", get(Self::ordinal))
.route("/output/:output", get(Self::output))
.route("/range/:start/:end", get(Self::range))
Expand Down Expand Up @@ -285,7 +284,7 @@ impl Server {

async fn ordinal(
index: extract::Extension<Arc<Index>>,
extract::Path(DeserializeOrdinalFromStr(ordinal)): extract::Path<DeserializeOrdinalFromStr>,
extract::Path(DeserializeFromStr(ordinal)): extract::Path<DeserializeFromStr<Ordinal>>,
) -> impl IntoResponse {
match index.blocktime(ordinal.height()) {
Ok(blocktime) => OrdinalHtml { ordinal, blocktime }.page().into_response(),
Expand Down Expand Up @@ -327,9 +326,10 @@ impl Server {
}

async fn range(
extract::Path((DeserializeOrdinalFromStr(start), DeserializeOrdinalFromStr(end))): extract::Path<
(DeserializeOrdinalFromStr, DeserializeOrdinalFromStr),
>,
extract::Path((DeserializeFromStr(start), DeserializeFromStr(end))): extract::Path<(
DeserializeFromStr<Ordinal>,
DeserializeFromStr<Ordinal>,
)>,
) -> impl IntoResponse {
match start.cmp(&end) {
Ordering::Equal => (StatusCode::BAD_REQUEST, Html("Empty Range".to_string())).into_response(),
Expand Down Expand Up @@ -538,6 +538,29 @@ impl Server {
}
}

async fn input(
extract::Extension(index): extract::Extension<Arc<Index>>,
extract::Path(path): extract::Path<(u64, usize, usize)>,
) -> Result<PageHtml, ServerError> {
let not_found =
|| ServerError::NotFound(format!("Input /{}/{}/{} unknown", path.0, path.1, path.2));

let block = index
.block(path.0)
.map_err(ServerError::InternalError)?
.ok_or_else(not_found)?;

let transaction = block.txdata.into_iter().nth(path.1).ok_or_else(not_found)?;

let input = transaction
.input
.into_iter()
.nth(path.2)
.ok_or_else(not_found)?;

Ok(InputHtml { path, input }.page())
}

async fn faq() -> impl IntoResponse {
Redirect::to("https://docs.ordinals.com/faq/")
}
Expand Down Expand Up @@ -1012,10 +1035,10 @@ mod tests {
test_server.assert_response_regex(
"/output/4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0",
StatusCode::OK,
".*<title>Output 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0</title>.*<h1>Output 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0</h1>
".*<title>Output 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0</title>.*<h1>Output <span class=monospace>4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b:0</span></h1>
<dl>
<dt>value</dt><dd>5000000000</dd>
<dt>script pubkey</dt><dd>OP_PUSHBYTES_65 04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f OP_CHECKSIG</dd>
<dt>script pubkey</dt><dd class=data>OP_PUSHBYTES_65 04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f OP_CHECKSIG</dd>
</dl>
<h2>1 Ordinal Range</h2>
<ul class=monospace>
Expand Down Expand Up @@ -1060,7 +1083,7 @@ mod tests {
<dt>block</dt><dd>1</dd>
</dl>
<h2>Latest Blocks</h2>
<ol start=1 reversed class='blocks monospace'>
<ol start=1 reversed class=blocks>
<li><a href=/block/[[:xdigit:]]{64}>[[:xdigit:]]{64}</a></li>
<li><a href=/block/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f>000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f</a></li>
</ol>.*",
Expand All @@ -1076,7 +1099,7 @@ mod tests {
test_server.assert_response_regex(
"/",
StatusCode::OK,
".*<ol start=101 reversed class='blocks monospace'>\n( <li><a href=/block/[[:xdigit:]]{64}>[[:xdigit:]]{64}</a></li>\n){100}</ol>.*"
".*<ol start=101 reversed class=blocks>\n( <li><a href=/block/[[:xdigit:]]{64}>[[:xdigit:]]{64}</a></li>\n){100}</ol>.*"
);
}

Expand Down Expand Up @@ -1147,13 +1170,13 @@ mod tests {
test_server.assert_response_regex(
&format!("/block/{block_hash}"),
StatusCode::OK,
".*<h1>Block [[:xdigit:]]{64}</h1>
".*<h1>Block <span class=monospace>[[:xdigit:]]{64}</span></h1>
<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>
<dt>prev blockhash</dt><dd><a href=/block/659f9b67fbc0b5cba0ef6ebc0aea322e1c246e29e43210bd581f5f3bd36d17bf class=monospace>659f9b67fbc0b5cba0ef6ebc0aea322e1c246e29e43210bd581f5f3bd36d17bf</a></dd>
</dl>
<h2>2 Transactions</h2>
<ul class=monospace>
Expand All @@ -1174,16 +1197,16 @@ mod tests {
&format!("/tx/{txid}"),
StatusCode::OK,
&format!(
".*<title>Transaction {txid}</title>.*<h1>Transaction {txid}</h1>
".*<title>Transaction {txid}</title>.*<h1>Transaction <span class=monospace>{txid}</span></h1>
<h2>1 Output</h2>
<ul class=monospace>
<li>
<a href=/output/9068a11b8769174363376b606af9a4b8b29dd7b13d013f4b0cbbd457db3c3ce5:0>
<a href=/output/9068a11b8769174363376b606af9a4b8b29dd7b13d013f4b0cbbd457db3c3ce5:0 class=monospace>
9068a11b8769174363376b606af9a4b8b29dd7b13d013f4b0cbbd457db3c3ce5:0
</a>
<dl>
<dt>value</dt><dd>5000000000</dd>
<dt>script pubkey</dt><dd></dd>
<dt>script pubkey</dt><dd class=data></dd>
</dl>
</li>
</ul>.*"
Expand Down Expand Up @@ -1215,4 +1238,22 @@ mod tests {
",
);
}

#[test]
fn input() {
TestServer::new().assert_response_regex(
"/input/0/0/0",
StatusCode::OK,
".*<title>Input /0/0/0</title>.*<h1>Input /0/0/0</h1>.*<dt>text</dt><dd>.*The Times 03/Jan/2009 Chancellor on brink of second bailout for banks</dd>.*",
);
}

#[test]
fn input_missing() {
TestServer::new().assert_response(
"/input/1/1/1",
StatusCode::NOT_FOUND,
"Input /1/1/1 unknown",
);
}
}
7 changes: 5 additions & 2 deletions src/subcommand/server/deserialize_ordinal_from_str.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use super::*;

pub(crate) struct DeserializeOrdinalFromStr(pub(crate) Ordinal);
pub(crate) struct DeserializeFromStr<T: FromStr>(pub(crate) T);

impl<'de> Deserialize<'de> for DeserializeOrdinalFromStr {
impl<'de, T: FromStr> Deserialize<'de> for DeserializeFromStr<T>
where
T::Err: Display,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
Expand Down
22 changes: 14 additions & 8 deletions src/subcommand/server/templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@ use {
html_escaper::{Escape, Trusted},
};

pub(crate) mod block;
pub(crate) mod clock;
pub(crate) mod home;
pub(crate) mod ordinal;
pub(crate) mod output;
pub(crate) mod range;
pub(crate) mod rare;
pub(crate) mod transaction;
pub(crate) use {
block::BlockHtml, clock::ClockSvg, home::HomeHtml, input::InputHtml, ordinal::OrdinalHtml,
output::OutputHtml, range::RangeHtml, rare::RareTxt, transaction::TransactionHtml,
};

mod block;
mod clock;
mod home;
mod input;
mod ordinal;
mod output;
mod range;
mod rare;
mod transaction;

#[derive(Boilerplate)]
pub(crate) struct PageHtml {
Expand Down
8 changes: 4 additions & 4 deletions src/subcommand/server/templates/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,23 @@ impl Content for BlockHtml {

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

#[test]
fn block_html() {
assert_eq!(
pretty_assert_eq!(
BlockHtml::new(bitcoin::blockdata::constants::genesis_block(
Network::Bitcoin
), Height(0))
.to_string(),
"
<h1>Block 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f</h1>
<h1>Block <span class=monospace>000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f</span></h1>
<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>
<dt>prev blockhash</dt><dd><a href=/block/0000000000000000000000000000000000000000000000000000000000000000 class=monospace>0000000000000000000000000000000000000000000000000000000000000000</a></dd>
</dl>
<h2>1 Transaction</h2>
<ul class=monospace>
Expand Down
Loading