Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 12c2769d2b | |||
| 9fd9873bc1 | |||
| a4b673d644 | |||
| d544e598e4 | |||
| 3091cd80fd | |||
| 846a9b94c7 | |||
| eb08b348b1 | |||
| cc7678ec18 | |||
| 0fbd04e791 | |||
| 12f77ce9af |
1962
Cargo.lock
generated
1962
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
11
Cargo.toml
@@ -8,5 +8,16 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
|
rocket = "0.5.1"
|
||||||
serde_json = "1.0.120"
|
serde_json = "1.0.120"
|
||||||
ureq = { version = "2.10.0", features = ["json"] }
|
ureq = { version = "2.10.0", features = ["json"] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies.rocket_dyn_templates]
|
||||||
|
version = "0.2.0"
|
||||||
|
features = ["tera"]
|
||||||
|
|
||||||
|
[dependencies.serde_with]
|
||||||
|
version = "1.9.1"
|
||||||
|
features = [ "chrono" ]
|
||||||
|
|||||||
70
src/lib.rs
70
src/lib.rs
@@ -1,7 +1,10 @@
|
|||||||
use chrono::prelude::*;
|
use chrono::{prelude::*, TimeDelta};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
use rocket::serde::Serialize;
|
||||||
use std::convert::Into;
|
use std::convert::Into;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
use serde_with::{serde_as, DisplayFromStr};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
pub enum BlockchainSupported {
|
pub enum BlockchainSupported {
|
||||||
BTC,
|
BTC,
|
||||||
@@ -10,12 +13,27 @@ pub enum BlockchainSupported {
|
|||||||
ETH,
|
ETH,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromStr for BlockchainSupported {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(val: &str) -> Result<Self, Self::Err> {
|
||||||
|
match val {
|
||||||
|
"BTC" => Ok(BlockchainSupported::BTC),
|
||||||
|
"BSV" => Ok(BlockchainSupported::BSV),
|
||||||
|
"XMR" => Ok(BlockchainSupported::XMR),
|
||||||
|
"ETH" => Ok(BlockchainSupported::ETH),
|
||||||
|
_ => Err("You picked an unsupported blockchain type! Create a patch and I'll probably accept it!".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type BlockHeight = i64;
|
type BlockHeight = i64;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
#[serde_as]
|
||||||
|
#[derive(Clone, Copy, PartialEq, Debug, Serialize)]
|
||||||
pub struct BlockchainInfo {
|
pub struct BlockchainInfo {
|
||||||
block_period: u64, // In seconds
|
block_period: u64, // In seconds
|
||||||
latest_block_height: BlockHeight,
|
latest_block_height: BlockHeight,
|
||||||
|
#[serde_as(as = "DisplayFromStr")]
|
||||||
latest_block_time: DateTime<Utc>,
|
latest_block_time: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +85,18 @@ pub fn time_to_blockheight(time: DateTime<Utc>, bi: BlockchainInfo) -> BlockHeig
|
|||||||
num_blocks + bi.latest_block_height
|
num_blocks + bi.latest_block_height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn blockheight_specific_chain_to_time(height: BlockHeight, blockchain: BlockchainSupported) -> DateTime<Utc> {
|
||||||
|
let bi: BlockchainInfo = blockchain.into();
|
||||||
|
blockheight_to_time(height, bi)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[deprecated = "The output of this function (Date & Time) will eventually be redundant, please look to change your own time keeping system to the superior blocktime as set out in this repository"]
|
||||||
|
pub fn blockheight_to_time(height: BlockHeight, bi: BlockchainInfo) -> DateTime<Utc> {
|
||||||
|
// TODO check the + and - are the right way around
|
||||||
|
let diff_seconds = (height - bi.latest_block_height) * bi.block_period as i64;
|
||||||
|
bi.latest_block_time + TimeDelta::seconds(diff_seconds)
|
||||||
|
}
|
||||||
|
|
||||||
// I know there's code duplication... I _could_ abstract it out.
|
// I know there's code duplication... I _could_ abstract it out.
|
||||||
// I think it's probably very YAGNI
|
// I think it's probably very YAGNI
|
||||||
|
|
||||||
@@ -157,10 +187,18 @@ pub fn update_xmr_blockheight() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_all_blockheights() {
|
||||||
|
update_bsv_blockheight();
|
||||||
|
update_btc_blockheight();
|
||||||
|
update_eth_blockheight();
|
||||||
|
update_xmr_blockheight();
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use chrono::TimeDelta;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use chrono::Duration;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
#[ignore]
|
||||||
@@ -219,6 +257,22 @@ mod tests {
|
|||||||
assert_eq!(result, 1001);
|
assert_eq!(result, 1001);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn back_and_forth() {
|
||||||
|
let latest_block_time = Utc.with_ymd_and_hms(2024, 10, 10, 10, 10, 0).unwrap();
|
||||||
|
let wanted_future_block_time = Utc.with_ymd_and_hms(2024, 10, 10, 10, 20, 1).unwrap();
|
||||||
|
let blockchain_info = BlockchainInfo {
|
||||||
|
block_period: 60 * 10, // 10 minutes
|
||||||
|
latest_block_height: 1000,
|
||||||
|
latest_block_time,
|
||||||
|
};
|
||||||
|
let result = time_to_blockheight(wanted_future_block_time, blockchain_info);
|
||||||
|
assert_eq!(result, 1001);
|
||||||
|
let future_time = blockheight_to_time(1001, blockchain_info);
|
||||||
|
assert_eq!(future_time, Utc.with_ymd_and_hms(2024, 10, 10, 10, 20, 0).unwrap());
|
||||||
|
// ignore the missing second... that second will not be needed in the future
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_block_time_previous() {
|
fn one_block_time_previous() {
|
||||||
let latest_block_time = Utc.with_ymd_and_hms(2024, 10, 10, 10, 20, 1).unwrap();
|
let latest_block_time = Utc.with_ymd_and_hms(2024, 10, 10, 10, 20, 1).unwrap();
|
||||||
@@ -250,23 +304,23 @@ mod tests {
|
|||||||
// Doesn't feel like an overly good test - but I can't think of anyother way that's "dynamic"
|
// Doesn't feel like an overly good test - but I can't think of anyother way that's "dynamic"
|
||||||
// to me manually updating the times above
|
// to me manually updating the times above
|
||||||
let bi: BlockchainInfo = BlockchainSupported::BTC.into();
|
let bi: BlockchainInfo = BlockchainSupported::BTC.into();
|
||||||
let wanted_time = bi.latest_block_time + Duration::seconds(bi.block_period as i64 + 1);
|
let wanted_time = bi.latest_block_time + TimeDelta::seconds(bi.block_period as i64 + 1);
|
||||||
let bh = time_to_blockheight_specific_chain(wanted_time, BlockchainSupported::BTC);
|
let bh = time_to_blockheight_specific_chain(wanted_time, BlockchainSupported::BTC);
|
||||||
assert_eq!(bh, bi.latest_block_height + 1);
|
assert_eq!(bh, bi.latest_block_height + 1);
|
||||||
let bi: BlockchainInfo = BlockchainSupported::BSV.into();
|
let bi: BlockchainInfo = BlockchainSupported::BSV.into();
|
||||||
let wanted_time = bi.latest_block_time + Duration::seconds(bi.block_period as i64 + 2);
|
let wanted_time = bi.latest_block_time + TimeDelta::seconds(bi.block_period as i64 + 2);
|
||||||
let bh = time_to_blockheight_specific_chain(wanted_time, BlockchainSupported::BSV);
|
let bh = time_to_blockheight_specific_chain(wanted_time, BlockchainSupported::BSV);
|
||||||
assert_eq!(bh, bi.latest_block_height + 1);
|
assert_eq!(bh, bi.latest_block_height + 1);
|
||||||
let bi: BlockchainInfo = BlockchainSupported::XMR.into();
|
let bi: BlockchainInfo = BlockchainSupported::XMR.into();
|
||||||
let wanted_time = bi.latest_block_time + Duration::seconds(bi.block_period as i64 + 3);
|
let wanted_time = bi.latest_block_time + TimeDelta::seconds(bi.block_period as i64 + 3);
|
||||||
let bh = time_to_blockheight_specific_chain(wanted_time, BlockchainSupported::XMR);
|
let bh = time_to_blockheight_specific_chain(wanted_time, BlockchainSupported::XMR);
|
||||||
assert_eq!(bh, bi.latest_block_height + 1);
|
assert_eq!(bh, bi.latest_block_height + 1);
|
||||||
let bi: BlockchainInfo = BlockchainSupported::ETH.into();
|
let bi: BlockchainInfo = BlockchainSupported::ETH.into();
|
||||||
let wanted_time = bi.latest_block_time + Duration::seconds(bi.block_period as i64 + 4);
|
let wanted_time = bi.latest_block_time + TimeDelta::seconds(bi.block_period as i64 + 4);
|
||||||
let bh = time_to_blockheight_specific_chain(wanted_time, BlockchainSupported::ETH);
|
let bh = time_to_blockheight_specific_chain(wanted_time, BlockchainSupported::ETH);
|
||||||
assert_eq!(bh, bi.latest_block_height + 1);
|
assert_eq!(bh, bi.latest_block_height + 1);
|
||||||
let bi: BlockchainInfo = BlockchainSupported::ETH.into();
|
let bi: BlockchainInfo = BlockchainSupported::ETH.into();
|
||||||
let wanted_time = bi.latest_block_time + Duration::seconds(bi.block_period as i64 + 5);
|
let wanted_time = bi.latest_block_time + TimeDelta::seconds(bi.block_period as i64 + 5);
|
||||||
let bh = time_to_blockheight_specific_chain(wanted_time, BlockchainSupported::XMR);
|
let bh = time_to_blockheight_specific_chain(wanted_time, BlockchainSupported::XMR);
|
||||||
assert_ne!(bh, bi.latest_block_height + 1);
|
assert_ne!(bh, bi.latest_block_height + 1);
|
||||||
}
|
}
|
||||||
|
|||||||
70
src/main.rs
Normal file
70
src/main.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#[macro_use] extern crate rocket;
|
||||||
|
use rocket_dyn_templates::{context, Template};
|
||||||
|
use blockchain_time::{BlockchainInfo, BlockchainSupported, update_all_blockheights, time_to_blockheight_specific_chain, blockheight_specific_chain_to_time};
|
||||||
|
use chrono::prelude::*;
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
fn index() -> Template {
|
||||||
|
let blockchain_infos: Vec<(String, BlockchainInfo)> = vec!{
|
||||||
|
("BSV".to_string(), BlockchainSupported::BSV.into()),
|
||||||
|
("BTC".to_string(), BlockchainSupported::BTC.into()),
|
||||||
|
("XMR".to_string(), BlockchainSupported::XMR.into()),
|
||||||
|
("ETH".to_string(), BlockchainSupported::ETH.into()),
|
||||||
|
};
|
||||||
|
let context = context! {
|
||||||
|
blockchain_infos
|
||||||
|
};
|
||||||
|
Template::render("index", context)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/update_blockchains")]
|
||||||
|
fn update_blockchains() -> Template {
|
||||||
|
update_all_blockheights();
|
||||||
|
Template::render("update_complete", context!{})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[get("/to_blockchain_time?<input_datetime>")]
|
||||||
|
fn to_blockchain_time(input_datetime: &str) -> Template {
|
||||||
|
let dt = NaiveDateTime::parse_from_str(input_datetime, "%FT%R");
|
||||||
|
let dt = match dt {
|
||||||
|
Ok(dt) => {
|
||||||
|
dt.and_utc()
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
let err_string = format!{"{}", e};
|
||||||
|
return Template::render("error", context!{ error: err_string });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let blocktimes = vec!{
|
||||||
|
("BSV".to_string(), time_to_blockheight_specific_chain(dt, BlockchainSupported::BSV)),
|
||||||
|
("BTC".to_string(), time_to_blockheight_specific_chain(dt, BlockchainSupported::BTC)),
|
||||||
|
("XMR".to_string(), time_to_blockheight_specific_chain(dt, BlockchainSupported::XMR)),
|
||||||
|
("ETH".to_string(), time_to_blockheight_specific_chain(dt, BlockchainSupported::ETH)),
|
||||||
|
};
|
||||||
|
let context = context!{
|
||||||
|
blocktimes
|
||||||
|
};
|
||||||
|
Template::render("blockchain_time_results", context)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/to_obsolete_time?<blockheight>&<blockchain>")]
|
||||||
|
fn to_obsolete_time(blockheight: i64, blockchain: &str) -> Template {
|
||||||
|
let blockchain: Result<BlockchainSupported, String> = blockchain.parse();
|
||||||
|
let blockchain = match blockchain {
|
||||||
|
Ok(bl) => bl,
|
||||||
|
Err(err) => {
|
||||||
|
return Template::render("error", context!{ error: err });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let context = context! {
|
||||||
|
time: blockheight_specific_chain_to_time(blockheight, blockchain)
|
||||||
|
};
|
||||||
|
Template::render("obsolete_time", context)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[launch]
|
||||||
|
fn rocket() -> _ {
|
||||||
|
rocket::build().mount("/", routes![index, update_blockchains, to_blockchain_time, to_obsolete_time])
|
||||||
|
.attach(Template::fairing())
|
||||||
|
}
|
||||||
22
templates/base.html.tera
Normal file
22
templates/base.html.tera
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Big Tim</title>
|
||||||
|
<style>
|
||||||
|
.blockchain_infos {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.blockchain_info {
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
.result {
|
||||||
|
font-family: monospace, monospace;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% block content %}{% endblock content %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
13
templates/blockchain_time_results.html.tera
Normal file
13
templates/blockchain_time_results.html.tera
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{% extends "base" %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>Big Tim Results</h1>
|
||||||
|
<div class="blockchain_infos">
|
||||||
|
{% for bts in blocktimes %}
|
||||||
|
<div class="blockchain_info">
|
||||||
|
<h2>{{bts[0]}}</h2>
|
||||||
|
<p>Height: <span class="result">{{bts[1]}}</span></p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<p>I hope that was useful for you! <a href="/">Go home</a> to try again.
|
||||||
|
{% endblock content %}
|
||||||
11
templates/error.html.tera
Normal file
11
templates/error.html.tera
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{% extends "base" %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>Error!</h1>
|
||||||
|
<p>Error: <span class="result">{{ error }}</span></p>
|
||||||
|
|
||||||
|
<p>I don't know what you did</p>
|
||||||
|
<p>Maybe that error above will make sense to you, maybe not</p>
|
||||||
|
<p>It's probably because you up a wrong DateTime in</p>
|
||||||
|
<p>Don't forget to put a time in - I don't know how to enforce this with HTML</p>
|
||||||
|
<p><a href="/">Please try again</a></p>
|
||||||
|
{% endblock content %}
|
||||||
45
templates/index.html.tera
Normal file
45
templates/index.html.tera
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{% extends "base" %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>Welcome to Blockchain Time</h1><p>A.K.A. Big Tim</p>
|
||||||
|
<div>
|
||||||
|
<h2>Convert to Blockchain Time</h2>
|
||||||
|
<form id="to_blocktmie" method="GET" action="to_blockchain_time">
|
||||||
|
<label for="input_datetime">Input DateTime (in UTC only currently)</label>
|
||||||
|
<input type="datetime-local" id="input_datetime" name="input_datetime">
|
||||||
|
<input type="submit" value="Submit">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 title="Boooooooo">Convert from Blockchain Time</h2>
|
||||||
|
<form id="to_obsolete_time" method="GET" action="to_obsolete_time">
|
||||||
|
<label for="blockheight">Input Block Height</label>
|
||||||
|
<input type="int" id="blockheight" name="blockheight" value="{{blockchain_infos[0][1].latest_block_height}}">
|
||||||
|
<label for="blockchain">Blockchain</label>
|
||||||
|
<select if="blockchain" name="blockchain">
|
||||||
|
<option value="BSV">BSV</option>
|
||||||
|
<option value="BTC">BTC</option>
|
||||||
|
<option value="XMR">XMR</option>
|
||||||
|
<option value="ETH">ETH</option>
|
||||||
|
</select>
|
||||||
|
<input type="submit" value="I'm a weenie that doesn't know my blocktimes">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="cur_blocks">
|
||||||
|
<h2>Latest Block Heights and Times Cached</h2>
|
||||||
|
<div class="blockchain_infos">
|
||||||
|
{% for bi in blockchain_infos %}
|
||||||
|
<div class="blockchain_info">
|
||||||
|
<h3>{{ bi[0] }}</h3>
|
||||||
|
<p>Blockheight: <span class="result">{{ bi[1].latest_block_height }}</span></p>
|
||||||
|
<p>Time Seen: <span class="result">{{ bi[1].latest_block_time }}</span></p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<a href="update_blockchains">Update Blockchain Information</a>
|
||||||
|
<p>Pressing the above button might take some time... or it might crash the appliaction...
|
||||||
|
or it'll do what you want... you won't know till you press it.</p>
|
||||||
|
<p>Please don't press it multiple times</p>
|
||||||
|
<p>In fact, maybe don't press it at all</p>
|
||||||
|
{% endblock content %}
|
||||||
7
templates/obsolete_time.html.tera
Normal file
7
templates/obsolete_time.html.tera
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{% extends "base" %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>BOOOOOOOOOOOOOOOOOOO</h1>
|
||||||
|
<h6>here's your obsolete time, in UTC, because timezones are boring to get correct, and no longer needed in the new blocktime order</h6>
|
||||||
|
<p class="result">{{time}}</p>
|
||||||
|
<p>Why don't you go back <a href="/">home</a> and try another thing</p>
|
||||||
|
{% endblock content %}
|
||||||
4
templates/update_complete.html.tera
Normal file
4
templates/update_complete.html.tera
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{% extends "base" %}
|
||||||
|
{% block content %}
|
||||||
|
If you're seeing this, it probably worked. Go back <a href="/">here</a> to check.
|
||||||
|
{% endblock content %}
|
||||||
Reference in New Issue
Block a user