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

Implement /r/blockinfo #3075

Merged
merged 16 commits into from
Feb 14, 2024
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
Loading