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 a clock #355

Merged
merged 16 commits into from
Aug 22, 2022
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