Skip to content

Commit

Permalink
Add /r/blockinfo endpoint (#3075)
Browse files Browse the repository at this point in the history
  • Loading branch information
jerryfane committed Feb 14, 2024
1 parent 14116a4 commit 6a0fc1a
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 8 deletions.
23 changes: 23 additions & 0 deletions docs/src/inscriptions/recursion.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ The recursive endpoints are:
- `/r/blockhash/<HEIGHT>`: block hash at given block height.
- `/r/blockhash`: latest block hash.
- `/r/blockheight`: latest block height.
- `/r/blockinfo/<QUERY>`: block info. `<QUERY>` may be a block height or block hash.
- `/r/blocktime`: UNIX time stamp of latest block.
- `/r/children/<INSCRIPTION_ID>`: the first 100 child inscription ids.
- `/r/children/<INSCRIPTION_ID>/<PAGE>`: the set of 100 child inscription ids on `<PAGE>`.
Expand Down Expand Up @@ -108,3 +109,25 @@ Examples
"page":49
}
```

- `/r/blockinfo/0`:

```json
{
"bits": 486604799,
"chainwork": 0,
"confirmations": 0,
"difficulty": 0.0,
"hash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
"height": 0,
"median_time": null,
"merkle_root": "0000000000000000000000000000000000000000000000000000000000000000",
"next_block": null,
"nonce": 0,
"previous_block": null,
"target": "00000000ffff0000000000000000000000000000000000000000000000000000",
"timestamp": 0,
"transaction_count": 0,
"version": 1
}
```
6 changes: 5 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use {
locktime::absolute::LockTime,
},
consensus::{self, Decodable, Encodable},
hash_types::BlockHash,
hash_types::{BlockHash, TxMerkleNode},
hashes::Hash,
opcodes,
script::{self, Instruction},
Expand Down Expand Up @@ -172,6 +172,10 @@ pub fn timestamp(seconds: u32) -> DateTime<Utc> {
Utc.timestamp_opt(seconds.into(), 0).unwrap()
}

fn target_as_block_hash(target: bitcoin::Target) -> BlockHash {
BlockHash::from_raw_hash(Hash::from_byte_array(target.to_le_bytes()))
}

fn unbound_outpoint() -> OutPoint {
OutPoint {
txid: Hash::all_zeros(),
Expand Down
123 changes: 121 additions & 2 deletions src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use {
crate::{
server_config::ServerConfig,
templates::{
BlockHtml, BlockJson, BlocksHtml, BlocksJson, ChildrenHtml, ChildrenJson, ClockSvg,
CollectionsHtml, HomeHtml, InputHtml, InscriptionHtml, InscriptionJson,
BlockHtml, BlockInfoJson, BlockJson, BlocksHtml, BlocksJson, ChildrenHtml, ChildrenJson,
ClockSvg, CollectionsHtml, HomeHtml, InputHtml, InscriptionHtml, InscriptionJson,
InscriptionsBlockHtml, InscriptionsHtml, InscriptionsJson, OutputHtml, OutputJson,
PageContent, PageHtml, PreviewAudioHtml, PreviewCodeHtml, PreviewFontHtml, PreviewImageHtml,
PreviewMarkdownHtml, PreviewModelHtml, PreviewPdfHtml, PreviewTextHtml, PreviewUnknownHtml,
Expand Down Expand Up @@ -124,6 +124,15 @@ impl Display for StaticHtml {
}
}

fn chainwork(chainwork: &[u8]) -> u128 {
chainwork
.iter()
.rev()
.enumerate()
.map(|(i, byte)| u128::from(*byte) * 256u128.pow(i.try_into().unwrap()))
.sum()
}

#[derive(Debug, Parser, Clone)]
pub struct Server {
#[arg(
Expand Down Expand Up @@ -255,6 +264,7 @@ impl Server {
)
.route("/r/blockheight", get(Self::block_height))
.route("/r/blocktime", get(Self::block_time))
.route("/r/blockinfo/:query", get(Self::block_info))
.route("/r/children/:inscription_id", get(Self::children_recursive))
.route(
"/r/children/:inscription_id/:page",
Expand Down Expand Up @@ -1054,6 +1064,49 @@ impl Server {
})
}

async fn block_info(
Extension(index): Extension<Arc<Index>>,
Path(DeserializeFromStr(query)): Path<DeserializeFromStr<BlockQuery>>,
) -> ServerResult<Json<BlockInfoJson>> {
task::block_in_place(|| {
let hash = match query {
BlockQuery::Hash(hash) => hash,
BlockQuery::Height(height) => index
.block_hash(Some(height))?
.ok_or_not_found(|| format!("block {height}"))?,
};

let info = index
.block_header_info(hash)?
.ok_or_not_found(|| format!("block {hash}"))?;

let header = index
.block_header(hash)?
.ok_or_not_found(|| format!("block {hash}"))?;

Ok(Json(BlockInfoJson {
bits: header.bits.to_consensus(),
chainwork: chainwork(&info.chainwork),
confirmations: info.confirmations,
difficulty: info.difficulty,
hash,
height: info.height.try_into().unwrap(),
median_time: info
.median_time
.map(|median_time| median_time.try_into().unwrap()),
merkle_root: info.merkle_root,
next_block: info.next_block_hash,
nonce: info.nonce,
previous_block: info.previous_block_hash,
target: target_as_block_hash(header.target()),
timestamp: info.time.try_into().unwrap(),
transaction_count: info.n_tx.try_into().unwrap(),
#[allow(clippy::cast_sign_loss)]
version: info.version.to_consensus() as u32,
}))
})
}

async fn block_time(Extension(index): Extension<Arc<Index>>) -> ServerResult<String> {
task::block_in_place(|| {
Ok(
Expand Down Expand Up @@ -5180,4 +5233,70 @@ next

server.assert_response(format!("/preview/{id}"), StatusCode::OK, "foo");
}

#[test]
fn chainwork_conversion_to_integer() {
assert_eq!(chainwork(&[]), 0);
assert_eq!(chainwork(&[1]), 1);
assert_eq!(chainwork(&[1, 0]), 256);
assert_eq!(chainwork(&[1, 1]), 257);
assert_eq!(chainwork(&[1, 0, 0]), 65536);
assert_eq!(chainwork(&[1, 0, 1]), 65537);
assert_eq!(chainwork(&[1, 1, 1]), 65793);
}

#[test]
fn block_info() {
let server = TestServer::new();

pretty_assert_eq!(
server.get_json::<BlockInfoJson>("/r/blockinfo/0"),
BlockInfoJson {
bits: 486604799,
chainwork: 0,
confirmations: 0,
difficulty: 0.0,
hash: "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
.parse()
.unwrap(),
height: 0,
median_time: None,
merkle_root: TxMerkleNode::all_zeros(),
next_block: None,
nonce: 0,
previous_block: None,
target: "00000000ffff0000000000000000000000000000000000000000000000000000"
.parse()
.unwrap(),
timestamp: 0,
transaction_count: 0,
version: 1,
},
);

server.mine_blocks(1);

pretty_assert_eq!(
server.get_json::<BlockInfoJson>("/r/blockinfo/1"),
BlockInfoJson {
bits: 0,
chainwork: 0,
confirmations: 0,
difficulty: 0.0,
hash: "56d05060a0280d0712d113f25321158747310ece87ea9e299bde06cf385b8d85"
.parse()
.unwrap(),
height: 1,
median_time: None,
merkle_root: TxMerkleNode::all_zeros(),
next_block: None,
nonce: 0,
previous_block: None,
target: BlockHash::all_zeros(),
timestamp: 0,
transaction_count: 0,
version: 1,
},
)
}
}
2 changes: 1 addition & 1 deletion src/templates.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use {super::*, boilerplate::Boilerplate};

pub(crate) use {
block::{BlockHtml, BlockJson},
block::{BlockHtml, BlockInfoJson, BlockJson},
blocks::{BlocksHtml, BlocksJson},
children::{ChildrenHtml, ChildrenJson},
clock::ClockSvg,
Expand Down
23 changes: 19 additions & 4 deletions src/templates/block.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
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,
Expand Down Expand Up @@ -61,6 +57,25 @@ impl BlockJson {
}
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct BlockInfoJson {
pub bits: u32,
pub chainwork: u128,
pub confirmations: i32,
pub difficulty: f64,
pub hash: BlockHash,
pub height: u32,
pub median_time: Option<u64>,
pub merkle_root: TxMerkleNode,
pub next_block: Option<BlockHash>,
pub nonce: u32,
pub previous_block: Option<BlockHash>,
pub target: BlockHash,
pub timestamp: u64,
pub transaction_count: u64,
pub version: u32,
}

impl PageContent for BlockHtml {
fn title(&self) -> String {
format!("Block {}", self.height)
Expand Down

0 comments on commit 6a0fc1a

Please sign in to comment.