Skip to content

Commit

Permalink
Recursive endpoints: /blockhash, /blockheight, /blocktime (#2175)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphjaph authored Jun 15, 2023
1 parent b69ec8b commit fffbef4
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 20 deletions.
9 changes: 8 additions & 1 deletion docs/src/inscriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,16 @@ This has a number of interesting use-cases:
- Publishing snippets of code, images, audio, or stylesheets as shared public
resources.

- Generative art collections where an algorithm is inscribed as JavaScript ,
- Generative art collections where an algorithm is inscribed as JavaScript,
and instantiated from multiple inscriptions with unique seeds.

- Generative profile picture collections where accessories and attributes are
inscribed as individual images, or in a shared texture atlas, and then
combined, collage-style, in unique combinations in multiple inscriptions.

A couple other endpoints that inscriptions may access are the following:

- `/blockheight`: latest block height.
- `/blockhash`: latest block hash.
- `/blockhash/<HEIGHT>`: block hash at given block height.
- `/blocktime`: UNIX time stamp of latest block.
6 changes: 6 additions & 0 deletions src/blocktime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ impl Blocktime {
}
}

pub(crate) fn unix_timestamp(self) -> i64 {
match self {
Self::Confirmed(timestamp) | Self::Expected(timestamp) => timestamp.timestamp(),
}
}

pub(crate) fn suffix(self) -> &'static str {
match self {
Self::Confirmed(_) => "",
Expand Down
20 changes: 12 additions & 8 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,14 +405,18 @@ impl Index {
.unwrap_or(0)
}

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

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

pub(crate) fn block_height(&self) -> Result<Option<Height>> {
self.begin_read()?.block_height()
}

pub(crate) fn block_hash(&self, height: Option<u64>) -> Result<Option<BlockHash>> {
self.begin_read()?.block_hash(height)
}

pub(crate) fn blocks(&self, take: usize) -> Result<Vec<(u64, BlockHash)>> {
let mut blocks = Vec::new();

Expand Down Expand Up @@ -660,7 +664,7 @@ impl Index {
}
}

pub(crate) fn blocktime(&self, height: Height) -> Result<Blocktime> {
pub(crate) fn block_time(&self, height: Height) -> Result<Blocktime> {
let height = height.n();

match self.get_block_by_height(height)? {
Expand Down Expand Up @@ -1010,21 +1014,21 @@ mod tests {
{
let context = Context::builder().args(["--height-limit", "0"]).build();
context.mine_blocks(1);
assert_eq!(context.index.height().unwrap(), None);
assert_eq!(context.index.block_height().unwrap(), None);
assert_eq!(context.index.block_count().unwrap(), 0);
}

{
let context = Context::builder().args(["--height-limit", "1"]).build();
context.mine_blocks(1);
assert_eq!(context.index.height().unwrap(), Some(Height(0)));
assert_eq!(context.index.block_height().unwrap(), Some(Height(0)));
assert_eq!(context.index.block_count().unwrap(), 1);
}

{
let context = Context::builder().args(["--height-limit", "2"]).build();
context.mine_blocks(2);
assert_eq!(context.index.height().unwrap(), Some(Height(1)));
assert_eq!(context.index.block_height().unwrap(), Some(Height(1)));
assert_eq!(context.index.block_count().unwrap(), 2);
}
}
Expand Down
23 changes: 22 additions & 1 deletion src/index/rtx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use super::*;
pub(crate) struct Rtx<'a>(pub(crate) redb::ReadTransaction<'a>);

impl Rtx<'_> {
pub(crate) fn height(&self) -> Result<Option<Height>> {
pub(crate) fn block_height(&self) -> Result<Option<Height>> {
Ok(
self
.0
Expand All @@ -27,4 +27,25 @@ impl Rtx<'_> {
.unwrap_or(0),
)
}

pub(crate) fn block_hash(&self, height: Option<u64>) -> Result<Option<BlockHash>> {
match height {
Some(height) => Ok(
self
.0
.open_table(HEIGHT_TO_BLOCK_HASH)?
.get(height)?
.map(|hash| BlockHash::load(*hash.value())),
),
None => Ok(
self
.0
.open_table(HEIGHT_TO_BLOCK_HASH)?
.range(0..)?
.rev()
.next()
.map(|(_height, hash)| BlockHash::load(*hash.value())),
),
}
}
}
108 changes: 102 additions & 6 deletions src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,12 @@ impl Server {

let router = Router::new()
.route("/", get(Self::home))
.route("/block-count", get(Self::block_count))
.route("/block/:query", get(Self::block))
.route("/blockcount", get(Self::block_count))
.route("/blockheight", get(Self::block_height))
.route("/blockhash", get(Self::block_hash))
.route("/blockhash/:height", get(Self::block_hash_from_height))
.route("/blocktime", get(Self::block_time))
.route("/bounties", get(Self::bounties))
.route("/clock", get(Self::clock))
.route("/content/:inscription_id", get(Self::content))
Expand Down Expand Up @@ -352,7 +356,7 @@ impl Server {
}

fn index_height(index: &Index) -> ServerResult<Height> {
index.height()?.ok_or_not_found(|| "genesis block")
index.block_height()?.ok_or_not_found(|| "genesis block")
}

async fn clock(Extension(index): Extension<Arc<Index>>) -> ServerResult<Response> {
Expand All @@ -379,7 +383,7 @@ impl Server {
SatHtml {
sat,
satpoint,
blocktime: index.blocktime(sat.height())?,
blocktime: index.block_time(sat.height())?,
inscription: index.get_inscription_id_by_sat(sat)?,
}
.page(page_config, index.has_sat_index()?),
Expand Down Expand Up @@ -678,6 +682,45 @@ impl Server {
Ok(index.block_count()?.to_string())
}

async fn block_height(Extension(index): Extension<Arc<Index>>) -> ServerResult<String> {
Ok(
index
.block_height()?
.ok_or_not_found(|| "blockheight")?
.to_string(),
)
}

async fn block_hash(Extension(index): Extension<Arc<Index>>) -> ServerResult<String> {
Ok(
index
.block_hash(None)?
.ok_or_not_found(|| "blockhash")?
.to_string(),
)
}

async fn block_hash_from_height(
Extension(index): Extension<Arc<Index>>,
Path(height): Path<u64>,
) -> ServerResult<String> {
Ok(
index
.block_hash(Some(height))?
.ok_or_not_found(|| "blockhash")?
.to_string(),
)
}

async fn block_time(Extension(index): Extension<Arc<Index>>) -> ServerResult<String> {
Ok(
index
.block_time(index.block_height()?.ok_or_not_found(|| "blocktime")?)?
.unix_timestamp()
.to_string(),
)
}

async fn input(
Extension(page_config): Extension<Arc<PageConfig>>,
Extension(index): Extension<Arc<Index>>,
Expand Down Expand Up @@ -749,7 +792,7 @@ impl Server {
);
headers.append(
header::CONTENT_SECURITY_POLICY,
HeaderValue::from_static("default-src *:*/content/ 'unsafe-eval' 'unsafe-inline' data:"),
HeaderValue::from_static("default-src *:*/content/ *:*/blockheight *:*/blockhash *:*/blockhash/ *:*/blocktime 'unsafe-eval' 'unsafe-inline' data:"),
);
headers.insert(
header::CACHE_CONTROL,
Expand Down Expand Up @@ -1400,19 +1443,72 @@ mod tests {
fn block_count_endpoint() {
let test_server = TestServer::new();

let response = test_server.get("/block-count");
let response = test_server.get("/blockcount");

assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.text().unwrap(), "1");

test_server.mine_blocks(1);

let response = test_server.get("/block-count");
let response = test_server.get("/blockcount");

assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.text().unwrap(), "2");
}

#[test]
fn block_height_endpoint() {
let test_server = TestServer::new();

let response = test_server.get("/blockheight");

assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.text().unwrap(), "0");

test_server.mine_blocks(2);

let response = test_server.get("/blockheight");

assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.text().unwrap(), "2");
}

#[test]
fn block_hash_endpoint() {
let test_server = TestServer::new();

let response = test_server.get("/blockhash");

assert_eq!(response.status(), StatusCode::OK);
assert_eq!(
response.text().unwrap(),
"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
);
}

#[test]
fn block_hash_from_height_endpoint() {
let test_server = TestServer::new();

let response = test_server.get("/blockhash/0");

assert_eq!(response.status(), StatusCode::OK);
assert_eq!(
response.text().unwrap(),
"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
);
}

#[test]
fn block_time_endpoint() {
let test_server = TestServer::new();

let response = test_server.get("/blocktime");

assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.text().unwrap(), "1231006505");
}

#[test]
fn range_end_before_range_start_returns_400() {
TestServer::new().assert_response(
Expand Down
4 changes: 2 additions & 2 deletions tests/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ fn inscription_content() {
.collect::<Vec<&http::HeaderValue>>(),
&[
"default-src 'self' 'unsafe-eval' 'unsafe-inline' data:",
"default-src *:*/content/ 'unsafe-eval' 'unsafe-inline' data:"
"default-src *:*/content/ *:*/blockheight *:*/blockhash *:*/blockhash/ *:*/blocktime 'unsafe-eval' 'unsafe-inline' data:",
]
);
assert_eq!(response.bytes().unwrap(), "FOO");
Expand Down Expand Up @@ -346,7 +346,7 @@ fn server_runs_with_rpc_user_and_pass_as_env_vars() {

rpc_server.mine_blocks(1);

let response = reqwest::blocking::get(format!("http://127.0.0.1:{port}/block-count")).unwrap();
let response = reqwest::blocking::get(format!("http://127.0.0.1:{port}/blockcount")).unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.text().unwrap(), "2");

Expand Down
4 changes: 2 additions & 2 deletions tests/test_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ impl TestServer {
let chain_block_count = client.get_block_count().unwrap() + 1;

for i in 0.. {
let response = reqwest::blocking::get(self.url().join("/block-count").unwrap()).unwrap();
let response = reqwest::blocking::get(self.url().join("/blockcount").unwrap()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
if response.text().unwrap().parse::<u64>().unwrap() == chain_block_count {
break;
Expand All @@ -84,7 +84,7 @@ impl TestServer {
let chain_block_count = client.get_block_count().unwrap() + 1;

for i in 0.. {
let response = reqwest::blocking::get(self.url().join("/block-count").unwrap()).unwrap();
let response = reqwest::blocking::get(self.url().join("/blockcount").unwrap()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
if response.text().unwrap().parse::<u64>().unwrap() == chain_block_count {
break;
Expand Down

0 comments on commit fffbef4

Please sign in to comment.