diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index becb38941c..de74bf844c 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -9,7 +9,7 @@ use { page_config::PageConfig, runes::Rune, templates::{ - BlockHtml, ClockSvg, HomeHtml, InputHtml, InscriptionHtml, InscriptionJson, + BlockHtml, BlockJson, ClockSvg, HomeHtml, InputHtml, InscriptionHtml, InscriptionJson, InscriptionsBlockHtml, InscriptionsHtml, InscriptionsJson, OutputHtml, OutputJson, PageContent, PageHtml, PreviewAudioHtml, PreviewCodeHtml, PreviewImageHtml, PreviewMarkdownHtml, PreviewModelHtml, PreviewPdfHtml, PreviewTextHtml, PreviewUnknownHtml, @@ -614,7 +614,8 @@ impl Server { Extension(page_config): Extension>, Extension(index): Extension>, Path(DeserializeFromStr(query)): Path>, - ) -> ServerResult> { + accept_json: AcceptJson, + ) -> ServerResult { let (block, height) = match query { BlockQuery::Height(height) => { let block = index @@ -636,10 +637,18 @@ impl Server { } }; - let (featured_inscriptions, total_num) = - index.get_highest_paying_inscriptions_in_block(height, 8)?; - - Ok( + Ok(if accept_json.0 { + let inscriptions = index.get_inscriptions_in_block(height)?; + Json(BlockJson::new( + block, + Height(height), + Self::index_height(&index)?, + inscriptions, + )) + .into_response() + } else { + let (featured_inscriptions, total_num) = + index.get_highest_paying_inscriptions_in_block(height, 8)?; BlockHtml::new( block, Height(height), @@ -647,8 +656,9 @@ impl Server { total_num, featured_inscriptions, ) - .page(page_config), - ) + .page(page_config) + .into_response() + }) } async fn transaction( diff --git a/src/templates.rs b/src/templates.rs index 5b5f456136..d170d28bcf 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -1,7 +1,7 @@ use {super::*, boilerplate::Boilerplate}; pub(crate) use { - block::BlockHtml, + block::{BlockHtml, BlockJson}, clock::ClockSvg, home::HomeHtml, iframe::Iframe, @@ -24,7 +24,7 @@ pub(crate) use { transaction::TransactionHtml, }; -mod block; +pub mod block; mod clock; mod home; mod iframe; diff --git a/src/templates/block.rs b/src/templates/block.rs index f9fc663a91..5534aa71d5 100644 --- a/src/templates/block.rs +++ b/src/templates/block.rs @@ -1,5 +1,9 @@ use super::*; +fn target_as_block_hash(target: bitcoin::Target) -> BlockHash { + BlockHash::from_raw_hash(Hash::from_byte_array(target.to_le_bytes())) +} + #[derive(Boilerplate)] pub(crate) struct BlockHtml { hash: BlockHash, @@ -7,7 +11,7 @@ pub(crate) struct BlockHtml { best_height: Height, block: Block, height: Height, - total_num_inscriptions: usize, + inscription_count: usize, featured_inscriptions: Vec, } @@ -16,21 +20,47 @@ impl BlockHtml { block: Block, height: Height, best_height: Height, - total_num_inscriptions: usize, + inscription_count: usize, featured_inscriptions: Vec, ) -> Self { Self { hash: block.header.block_hash(), - target: BlockHash::from_raw_hash(Hash::from_byte_array(block.header.target().to_be_bytes())), + target: target_as_block_hash(block.header.target()), block, height, best_height, - total_num_inscriptions, + inscription_count, featured_inscriptions, } } } +#[derive(Debug, PartialEq, Serialize, Deserialize)] +pub struct BlockJson { + pub hash: BlockHash, + pub target: BlockHash, + pub best_height: u64, + pub height: u64, + pub inscriptions: Vec, +} + +impl BlockJson { + pub(crate) fn new( + block: Block, + height: Height, + best_height: Height, + inscriptions: Vec, + ) -> Self { + Self { + hash: block.header.block_hash(), + target: target_as_block_hash(block.header.target()), + height: height.0, + best_height: best_height.0, + inscriptions, + } + } +} + impl PageContent for BlockHtml { fn title(&self) -> String { format!("Block {}", self.height) @@ -103,4 +133,12 @@ mod tests { r"

Block 1

.*\s*next.*", ); } + + #[test] + fn block_hash_serializes_as_hex_string() { + assert_eq!( + serde_json::to_string(&BlockHash::all_zeros()).unwrap(), + "\"0000000000000000000000000000000000000000000000000000000000000000\"" + ); + } } diff --git a/templates/block.html b/templates/block.html index dd9b1063a4..b13fd560ea 100644 --- a/templates/block.html +++ b/templates/block.html @@ -21,13 +21,13 @@

Block {{ self.height }}

next %% } -

{{"Inscription".tally(self.total_num_inscriptions)}}

+

{{"Inscription".tally(self.inscription_count)}}

%% for id in &self.featured_inscriptions { {{ Iframe::thumbnail(*id) }} %% }
-%% if &self.total_num_inscriptions > &self.featured_inscriptions.len() { +%% if &self.inscription_count > &self.featured_inscriptions.len() { diff --git a/tests/json_api.rs b/tests/json_api.rs index 296ac2286f..86b46b07c0 100644 --- a/tests/json_api.rs +++ b/tests/json_api.rs @@ -1,4 +1,4 @@ -use super::*; +use {super::*, bitcoin::BlockHash}; #[test] fn get_sat_without_sat_index() { @@ -392,3 +392,32 @@ fn json_request_fails_when_not_enabled() { assert_eq!(response.status(), StatusCode::NOT_ACCEPTABLE); } + +#[test] +fn get_block() { + let rpc_server = test_bitcoincore_rpc::spawn(); + + rpc_server.mine_blocks(1); + + let response = + TestServer::spawn_with_args(&rpc_server, &["--enable-json-api"]).json_request("/block/0"); + + assert_eq!(response.status(), StatusCode::OK); + + let block_json: BlockJson = serde_json::from_str(&response.text().unwrap()).unwrap(); + + assert_eq!( + block_json, + BlockJson { + hash: "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" + .parse::() + .unwrap(), + target: "00000000ffff0000000000000000000000000000000000000000000000000000" + .parse::() + .unwrap(), + best_height: 1, + height: 0, + inscriptions: vec![], + } + ); +} diff --git a/tests/lib.rs b/tests/lib.rs index 4f66f725c1..52eb296fc7 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -12,8 +12,8 @@ use { inscription_id::InscriptionId, rarity::Rarity, templates::{ - inscription::InscriptionJson, inscriptions::InscriptionsJson, output::OutputJson, - sat::SatJson, + block::BlockJson, inscription::InscriptionJson, inscriptions::InscriptionsJson, + output::OutputJson, sat::SatJson, }, SatPoint, },