Skip to content

Commit

Permalink
Tick tock next block (#355)
Browse files Browse the repository at this point in the history
The SVG is adapted from this extremely helpful tutorial by John O. Paul:

https://medium.com/the-andela-way/create-a-pure-css-clock-with-svg-f123bcc41e46
  • Loading branch information
casey authored Aug 22, 2022
1 parent 3075de5 commit e8e8529
Show file tree
Hide file tree
Showing 10 changed files with 241 additions and 37 deletions.
12 changes: 9 additions & 3 deletions src/epoch.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::*;

#[derive(Copy, Clone, Eq, PartialEq, Debug, Display)]
#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, PartialOrd)]
pub(crate) struct Epoch(pub(crate) u64);

impl Epoch {
Expand Down Expand Up @@ -40,11 +40,11 @@ impl Epoch {
Ordinal(2099999997480000),
Ordinal(Ordinal::SUPPLY),
];

pub(crate) const FIRST_POST_SUBSIDY: Epoch = Self(33);
pub(crate) const BLOCKS: u64 = 210000;

pub(crate) fn subsidy(self) -> u64 {
if self.0 < 64 {
if self < Self::FIRST_POST_SUBSIDY {
(50 * COIN_VALUE) >> self.0
} else {
0
Expand Down Expand Up @@ -158,4 +158,10 @@ mod tests {
assert_eq!(Epoch(0), 0);
assert_eq!(Epoch(100), 100);
}

#[test]
fn first_post_subsidy() {
assert_eq!(Epoch::FIRST_POST_SUBSIDY.subsidy(), 0);
assert!((Epoch(Epoch::FIRST_POST_SUBSIDY.0 - 1)).subsidy() > 0);
}
}
15 changes: 14 additions & 1 deletion src/height.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::*;

#[derive(Copy, Clone, Debug, Display, FromStr)]
#[derive(Copy, Clone, Debug, Display, FromStr, Ord, Eq, PartialEq, PartialOrd)]
pub(crate) struct Height(pub(crate) u64);

impl Height {
Expand All @@ -18,6 +18,10 @@ impl Height {
let epoch_starting_height = epoch.starting_height();
epoch_starting_ordinal + (self - epoch_starting_height.n()).n() * epoch.subsidy()
}

pub(crate) fn period_offset(self) -> u64 {
self.0 % PERIOD_BLOCKS
}
}

impl Add<u64> for Height {
Expand Down Expand Up @@ -103,4 +107,13 @@ mod tests {
*Epoch::STARTING_ORDINALS.last().unwrap()
);
}

#[test]
fn period_offset() {
assert_eq!(Height(0).period_offset(), 0);
assert_eq!(Height(1).period_offset(), 1);
assert_eq!(Height(2015).period_offset(), 2015);
assert_eq!(Height(2016).period_offset(), 0);
assert_eq!(Height(2017).period_offset(), 1);
}
}
6 changes: 3 additions & 3 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,8 @@ impl Index {
Ok(rtx::Rtx(self.database.begin_read()?))
}

pub(crate) fn height(&self) -> Result<u64> {
self.begin_read()?.height()
pub(crate) fn height(&self) -> Result<Height> {
Ok(Height(self.begin_read()?.height()?))
}

pub(crate) fn blocks(&self, take: u64) -> Result<Vec<(u64, BlockHash)>> {
Expand Down Expand Up @@ -370,7 +370,7 @@ impl Index {
}

pub(crate) fn find(&self, ordinal: Ordinal) -> Result<Option<SatPoint>> {
if self.height()? < ordinal.height().0 {
if self.height()? < ordinal.height() {
return Ok(None);
}

Expand Down
24 changes: 21 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,24 @@ use {
tower_http::cors::{Any, CorsLayer},
};

const PERIOD_BLOCKS: u64 = 2016;
const CYCLE_EPOCHS: u64 = 6;
#[cfg(test)]
use regex::Regex;

#[cfg(test)]
macro_rules! assert_regex_match {
($string:expr, $pattern:expr $(,)?) => {
let pattern: &'static str = $pattern;
let regex = Regex::new(&format!("^(?s){}$", pattern)).unwrap();
let string = $string;

if !regex.is_match(string.as_ref()) {
panic!(
"Regex:\n\n{}\n\n…did not match string:\n\n{}",
regex, string
);
}
};
}

mod arguments;
mod blocktime;
Expand All @@ -92,8 +108,10 @@ mod subcommand;

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

static INTERRUPTS: AtomicU64 = AtomicU64::new(0);
const PERIOD_BLOCKS: u64 = 2016;
const CYCLE_EPOCHS: u64 = 6;

static INTERRUPTS: AtomicU64 = AtomicU64::new(0);
static LISTENERS: Mutex<Vec<Handle>> = Mutex::new(Vec::new());

fn main() {
Expand Down
27 changes: 24 additions & 3 deletions src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use {
self::{
deserialize_ordinal_from_str::DeserializeOrdinalFromStr,
templates::{
block::BlockHtml, home::HomeHtml, ordinal::OrdinalHtml, output::OutputHtml, range::RangeHtml,
transaction::TransactionHtml, Content,
block::BlockHtml, clock::ClockSvg, home::HomeHtml, ordinal::OrdinalHtml, output::OutputHtml,
range::RangeHtml, transaction::TransactionHtml, Content,
},
},
axum::{body, http::header, response::Response},
Expand Down Expand Up @@ -96,6 +96,8 @@ impl Server {
.route("/", get(Self::home))
.route("/block/:hash", get(Self::block))
.route("/bounties", get(Self::bounties))
.route("/clock", get(Self::clock))
.route("/clock.svg", get(Self::clock))
.route("/faq", get(Self::faq))
.route("/favicon.ico", get(Self::favicon))
.route("/height", get(Self::height))
Expand Down Expand Up @@ -180,14 +182,33 @@ impl Server {
}
}

async fn clock(index: extract::Extension<Arc<Index>>) -> impl IntoResponse {
match index.height() {
Ok(height) => ClockSvg::new(height).into_response(),
Err(err) => {
eprintln!("Failed to retrieve height from index: {err}");
(
StatusCode::INTERNAL_SERVER_ERROR,
Html(
StatusCode::INTERNAL_SERVER_ERROR
.canonical_reason()
.unwrap_or_default()
.to_string(),
),
)
.into_response()
}
}
}

async fn ordinal(
index: extract::Extension<Arc<Index>>,
extract::Path(DeserializeOrdinalFromStr(ordinal)): extract::Path<DeserializeOrdinalFromStr>,
) -> impl IntoResponse {
match index.blocktime(ordinal.height()) {
Ok(blocktime) => OrdinalHtml { ordinal, blocktime }.page().into_response(),
Err(err) => {
eprintln!("Failed to retrieve height from index: {err}");
eprintln!("Failed to retrieve blocktime from index: {err}");
(
StatusCode::INTERNAL_SERVER_ERROR,
Html(
Expand Down
1 change: 1 addition & 0 deletions src/subcommand/server/templates.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use {super::*, boilerplate::Display};

pub(crate) mod block;
pub(crate) mod clock;
pub(crate) mod home;
pub(crate) mod ordinal;
pub(crate) mod output;
Expand Down
93 changes: 93 additions & 0 deletions src/subcommand/server/templates/clock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use super::*;

#[derive(Display)]
pub(crate) struct ClockSvg {
hour: f64,
minute: f64,
second: f64,
}

impl ClockSvg {
pub(crate) fn new(height: Height) -> Self {
let min = height.min(Epoch::FIRST_POST_SUBSIDY.starting_height());

Self {
hour: (min.n() % Epoch::FIRST_POST_SUBSIDY.starting_height().n()) as f64
/ Epoch::FIRST_POST_SUBSIDY.starting_height().n() as f64
* 360.0,
minute: (min.n() % Epoch::BLOCKS) as f64 / Epoch::BLOCKS as f64 * 360.0,
second: height.period_offset() as f64 / PERIOD_BLOCKS as f64 * 360.0,
}
}
}

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

#[test]
fn second() {
assert_eq!(ClockSvg::new(Height(0)).second, 0.0);
assert_eq!(ClockSvg::new(Height(504)).second, 90.0);
assert_eq!(ClockSvg::new(Height(1008)).second, 180.0);
assert_eq!(ClockSvg::new(Height(1512)).second, 270.0);
assert_eq!(ClockSvg::new(Height(2016)).second, 0.0);
assert_eq!(ClockSvg::new(Height(6930000)).second, 180.0);
assert_eq!(ClockSvg::new(Height(6930504)).second, 270.0);
}

#[test]
fn minute() {
assert_eq!(ClockSvg::new(Height(0)).minute, 0.0);
assert_eq!(ClockSvg::new(Height(52500)).minute, 90.0);
assert_eq!(ClockSvg::new(Height(105000)).minute, 180.0);
assert_eq!(ClockSvg::new(Height(157500)).minute, 270.0);
assert_eq!(ClockSvg::new(Height(210000)).minute, 0.0);
assert_eq!(ClockSvg::new(Height(6930000)).minute, 0.0);
assert_eq!(ClockSvg::new(Height(6930001)).minute, 0.0);
}

#[test]
fn hour() {
assert_eq!(ClockSvg::new(Height(0)).hour, 0.0);
assert_eq!(ClockSvg::new(Height(1732500)).hour, 90.0);
assert_eq!(ClockSvg::new(Height(3465000)).hour, 180.0);
assert_eq!(ClockSvg::new(Height(5197500)).hour, 270.0);
assert_eq!(ClockSvg::new(Height(6930000)).hour, 0.0);
assert_eq!(ClockSvg::new(Height(6930001)).hour, 0.0);
}

#[test]
fn final_subsidy_height() {
assert_eq!(
ClockSvg::new(Height(6929999)).second,
1007.0 / 2016.0 * 360.0
);
assert_eq!(
ClockSvg::new(Height(6929999)).minute,
209_999.0 / 210_000.0 * 360.0
);
assert_eq!(
ClockSvg::new(Height(6929999)).hour,
6929999.0 / 6930000.0 * 360.0
);
}

#[test]
fn first_post_subsidy_height() {
assert_eq!(ClockSvg::new(Height(6930000)).second, 180.0);
assert_eq!(ClockSvg::new(Height(6930000)).minute, 0.0);
assert_eq!(ClockSvg::new(Height(6930000)).hour, 0.0);
}

#[test]
fn foo_svg() {
assert_regex_match!(
ClockSvg::new(Height(6929999)).to_string(),
r##"<svg.*<line y2="-9" transform="rotate\(359.9999480519481\)"/>
<line y2="-13" stroke-width="0.6" transform="rotate\(359.9982857142857\)"/>
<line y2="-16" stroke="#d00505" stroke-width="0.2" transform="rotate\(179.82142857142858\)"/>.*</svg>
"##,
);
}
}
34 changes: 10 additions & 24 deletions src/subcommand/server/templates/home.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,33 +30,11 @@ impl Content for HomeHtml {

#[cfg(test)]
mod tests {
use {super::*, regex::Regex};

macro_rules! assert_regex_match {
($pattern:expr, $string:expr $(,)?) => {
let regex = Regex::new(&format!("^(?s){}$", $pattern)).unwrap();
let string = $string;

if !regex.is_match(string) {
panic!(
"Regex:\n\n{}\n\n…did not match string:\n\n{}",
regex, string
);
}
};
}
use super::*;

#[test]
fn home_html() {
assert_regex_match!(
"<h1>Ordinals</h1>
<nav>.*</nav>
<h2>Recent Blocks</h2>
<ol start=1 reversed class=monospace>
<li><a href=/block/1{64} class=uncommon>1{64}</a></li>
<li><a href=/block/0{64} class=mythic>0{64}</a></li>
</ol>
",
&HomeHtml::new(vec![
(
1,
Expand All @@ -71,7 +49,15 @@ mod tests {
.unwrap()
)
],)
.to_string()
.to_string(),
"<h1>Ordinals</h1>
<nav>.*</nav>
<h2>Recent Blocks</h2>
<ol start=1 reversed class=monospace>
<li><a href=/block/1{64} class=uncommon>1{64}</a></li>
<li><a href=/block/0{64} class=mythic>0{64}</a></li>
</ol>
",
);
}
}
41 changes: 41 additions & 0 deletions templates/clock.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit e8e8529

Please sign in to comment.