Changeset View
Changeset View
Standalone View
Standalone View
chronik/chronik-indexer/src/query/blocks.rs
| // Copyright (c) 2023 The Bitcoin developers | // Copyright (c) 2023 The Bitcoin developers | ||||
| // Distributed under the MIT software license, see the accompanying | // Distributed under the MIT software license, see the accompanying | ||||
| // file COPYING or http://www.opensource.org/licenses/mit-license.php. | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
| //! Module for [`QueryBlocks`], to query blocks. | //! Module for [`QueryBlocks`], to query blocks. | ||||
| use abc_rust_error::{Result, WrapErr}; | use abc_rust_error::{Result, WrapErr}; | ||||
| use bitcoinsuite_core::{ | use bitcoinsuite_core::{ | ||||
| block::BlockHash, | block::BlockHash, | ||||
| hash::{Hashed, Sha256d}, | |||||
| tx::{Tx, TxId}, | tx::{Tx, TxId}, | ||||
| }; | }; | ||||
| use chronik_bridge::ffi; | use chronik_bridge::ffi; | ||||
| use chronik_db::{ | use chronik_db::{ | ||||
| db::Db, | db::Db, | ||||
| io::{ | io::{ | ||||
| BlockHeight, BlockReader, BlockStats, BlockStatsReader, DbBlock, | BlockHeight, BlockReader, BlockStats, BlockStatsReader, DbBlock, | ||||
| SpentByReader, TxNum, TxReader, | SpentByReader, TxNum, TxReader, | ||||
| }, | }, | ||||
| mem::Mempool, | mem::Mempool, | ||||
| }; | }; | ||||
| use chronik_plugin::data::PluginNameMap; | use chronik_plugin::data::PluginNameMap; | ||||
| use chronik_proto::proto; | use chronik_proto::proto; | ||||
| use thiserror::Error; | use thiserror::Error; | ||||
| use tokio::sync::Mutex; | |||||
| use crate::{ | use crate::{ | ||||
| avalanche::Avalanche, | avalanche::Avalanche, | ||||
| indexer::Node, | indexer::Node, | ||||
| merkle::BlockMerkleTree, | |||||
| query::{ | query::{ | ||||
| make_tx_proto, read_plugin_outputs, HashOrHeight, MakeTxProtoParams, | make_tx_proto, read_plugin_outputs, HashOrHeight, MakeTxProtoParams, | ||||
| OutputsSpent, TxTokenData, | OutputsSpent, TxTokenData, | ||||
| }, | }, | ||||
| }; | }; | ||||
| const MAX_BLOCKS_PAGE_SIZE: usize = 500; | const MAX_BLOCKS_PAGE_SIZE: usize = 500; | ||||
| Show All 12 Lines | pub struct QueryBlocks<'a> { | ||||
| /// Mempool | /// Mempool | ||||
| pub mempool: &'a Mempool, | pub mempool: &'a Mempool, | ||||
| /// Access to bitcoind to read txs | /// Access to bitcoind to read txs | ||||
| pub node: &'a Node, | pub node: &'a Node, | ||||
| /// Whether the SLP/ALP token index is enabled | /// Whether the SLP/ALP token index is enabled | ||||
| pub is_token_index_enabled: bool, | pub is_token_index_enabled: bool, | ||||
| /// Map plugin name <-> plugin idx of all loaded plugins | /// Map plugin name <-> plugin idx of all loaded plugins | ||||
| pub plugin_name_map: &'a PluginNameMap, | pub plugin_name_map: &'a PluginNameMap, | ||||
| /// Cached block merkle tree | |||||
| pub block_merkle_tree: &'a Mutex<BlockMerkleTree>, | |||||
| } | } | ||||
| /// Errors indicating something went wrong with querying blocks. | /// Errors indicating something went wrong with querying blocks. | ||||
| #[derive(Debug, Error, PartialEq)] | #[derive(Debug, Error, PartialEq)] | ||||
| pub enum QueryBlockError { | pub enum QueryBlockError { | ||||
| /// Block not found in DB | /// Block not found in DB | ||||
| #[error("404: Block not found: {0}")] | #[error("404: Block not found: {0}")] | ||||
| BlockNotFound(String), | BlockNotFound(String), | ||||
| /// Invalid block start height | /// Invalid block start height | ||||
| #[error("400: Invalid block start height: {0}")] | #[error("400: Invalid block start height: {0}")] | ||||
| InvalidStartHeight(BlockHeight), | InvalidStartHeight(BlockHeight), | ||||
| /// Invalid block end height | /// Invalid block end height | ||||
| #[error("400: Invalid block end height: {0}")] | #[error("400: Invalid block end height: {0}")] | ||||
| InvalidEndHeight(BlockHeight), | InvalidEndHeight(BlockHeight), | ||||
| /// Invalid checkpoint height | |||||
| #[error( | |||||
| "400: Invalid checkpoint height {0}, may not be below queried header \ | |||||
| height {1}" | |||||
| )] | |||||
| InvalidCheckpointHeight(BlockHeight, BlockHeight), | |||||
| /// Blocks page size too large | /// Blocks page size too large | ||||
| #[error( | #[error( | ||||
| "400: Blocks page size too large, may not be above {} but got {0}", | "400: Blocks page size too large, may not be above {} but got {0}", | ||||
| MAX_BLOCKS_PAGE_SIZE | MAX_BLOCKS_PAGE_SIZE | ||||
| )] | )] | ||||
| BlocksPageSizeTooLarge(usize), | BlocksPageSizeTooLarge(usize), | ||||
| /// DB is missing block stats | /// DB is missing block stats | ||||
| ▲ Show 20 Lines • Show All 220 Lines • ▼ Show 20 Lines | ) -> proto::BlockInfo { | ||||
| num_outputs: block_stats.num_outputs, | num_outputs: block_stats.num_outputs, | ||||
| sum_input_sats: block_stats.sum_input_sats, | sum_input_sats: block_stats.sum_input_sats, | ||||
| sum_coinbase_output_sats: block_stats.sum_coinbase_output_sats, | sum_coinbase_output_sats: block_stats.sum_coinbase_output_sats, | ||||
| sum_normal_output_sats: block_stats.sum_normal_output_sats, | sum_normal_output_sats: block_stats.sum_normal_output_sats, | ||||
| sum_burned_sats: block_stats.sum_burned_sats, | sum_burned_sats: block_stats.sum_burned_sats, | ||||
| } | } | ||||
| } | } | ||||
| /// Query a block header | /// Query a block header. Optional checkpoint data is computed and | ||||
| pub fn header(&self, hash_or_height: String) -> Result<proto::BlockHeader> { | /// included in the result if checkpoint_height > 0. | ||||
| pub async fn header( | |||||
| &self, | |||||
| hash_or_height: String, | |||||
| checkpoint_height: i32, | |||||
| ) -> Result<proto::BlockHeader> { | |||||
| let bridge = &self.node.bridge; | let bridge = &self.node.bridge; | ||||
| let block_index = match hash_or_height.parse::<HashOrHeight>()? { | let block_index = match hash_or_height.parse::<HashOrHeight>()? { | ||||
| HashOrHeight::Hash(hash) => { | HashOrHeight::Hash(hash) => { | ||||
| bridge.lookup_block_index(hash.to_bytes()) | bridge.lookup_block_index(hash.to_bytes()) | ||||
| } | } | ||||
| HashOrHeight::Height(height) => { | HashOrHeight::Height(height) => { | ||||
| bridge.lookup_block_index_by_height(height) | bridge.lookup_block_index_by_height(height) | ||||
| } | } | ||||
| }; | }; | ||||
| let block_index = | let block_index = | ||||
| block_index.map_err(|_| BlockNotFound(hash_or_height))?; | block_index.map_err(|_| BlockNotFound(hash_or_height))?; | ||||
| let mut root: Vec<u8> = Vec::new(); | |||||
| let mut branch: Vec<Vec<u8>> = Vec::new(); | |||||
| if checkpoint_height > 0 { | |||||
| let block_height = ffi::get_block_info(block_index).height; | |||||
| if block_height > checkpoint_height { | |||||
| return Err(InvalidCheckpointHeight( | |||||
| checkpoint_height, | |||||
| block_height, | |||||
| ) | |||||
| .into()); | |||||
| } | |||||
| let hashes: Vec<Sha256d> = bridge | |||||
| .get_block_hashes_by_range(0, checkpoint_height)? | |||||
| .iter() | |||||
| .map(|raw_hash| Sha256d::from_le_bytes(raw_hash.data)) | |||||
| .collect(); | |||||
| let mut block_merkle_tree = self.block_merkle_tree.lock().await; | |||||
| let (root_hash, branch_hashes) = block_merkle_tree | |||||
| .merkle_root_and_branch(&hashes, block_height as usize); | |||||
| root = root_hash.to_le_bytes().to_vec(); | |||||
| branch = branch_hashes | |||||
| .iter() | |||||
| .map(|&hash| hash.to_le_bytes().to_vec()) | |||||
| .collect(); | |||||
| } | |||||
| Ok(proto::BlockHeader { | Ok(proto::BlockHeader { | ||||
| raw_header: ffi::get_block_header(block_index).to_vec(), | raw_header: ffi::get_block_header(block_index).to_vec(), | ||||
| root, | |||||
| branch, | |||||
| }) | }) | ||||
| } | } | ||||
| /// Query headers by a range of heights. Start and end height are inclusive. | /// Query headers by a range of heights. Start and end height are inclusive. | ||||
| pub fn headers_by_range( | /// Optional checkpoint data is computed and included with the last header | ||||
| /// if checkpoint_height > 0. | |||||
| pub async fn headers_by_range( | |||||
| &self, | &self, | ||||
| start_height: BlockHeight, | start_height: BlockHeight, | ||||
| end_height: BlockHeight, | end_height: BlockHeight, | ||||
| checkpoint_height: i32, | |||||
| ) -> Result<proto::BlockHeaders> { | ) -> Result<proto::BlockHeaders> { | ||||
| Self::check_range_boundaries(start_height, end_height)?; | Self::check_range_boundaries(start_height, end_height)?; | ||||
| let mut root: Vec<u8> = Vec::new(); | |||||
| let mut branch: Vec<Vec<u8>> = Vec::new(); | |||||
| if checkpoint_height > 0 { | |||||
| if end_height > checkpoint_height { | |||||
| return Err(InvalidCheckpointHeight( | |||||
| checkpoint_height, | |||||
| end_height, | |||||
| ) | |||||
| .into()); | |||||
| } | |||||
| let bridge = &self.node.bridge; | |||||
| let hashes: Vec<Sha256d> = bridge | |||||
| .get_block_hashes_by_range(0, checkpoint_height)? | |||||
| .iter() | |||||
| .map(|raw_hash| Sha256d::from_le_bytes(raw_hash.data)) | |||||
| .collect(); | |||||
| let mut block_merkle_tree = self.block_merkle_tree.lock().await; | |||||
| let (root_hash, branch_hashes) = block_merkle_tree | |||||
| .merkle_root_and_branch(&hashes, end_height as usize); | |||||
| root = root_hash.to_le_bytes().to_vec(); | |||||
| branch = branch_hashes | |||||
| .iter() | |||||
| .map(|&hash| hash.to_le_bytes().to_vec()) | |||||
| .collect(); | |||||
| } | |||||
| let unset_merkle_root: Vec<u8> = Vec::new(); | |||||
| let unset_merkle_branch: Vec<Vec<u8>> = Vec::new(); | |||||
| let headers = self | let headers = self | ||||
| .node | .node | ||||
| .bridge | .bridge | ||||
| .get_block_headers_by_range(start_height, end_height)?; | .get_block_headers_by_range(start_height, end_height)?; | ||||
| let headers = headers | let last_header = headers.last(); | ||||
| let mut headers: Vec<_> = headers | |||||
| .iter() | .iter() | ||||
| .take(headers.len() - 1) | |||||
| .map(|h| proto::BlockHeader { | .map(|h| proto::BlockHeader { | ||||
| raw_header: h.data.to_vec(), | raw_header: h.data.to_vec(), | ||||
| root: unset_merkle_root.clone(), | |||||
| branch: unset_merkle_branch.clone(), | |||||
| }) | }) | ||||
| .collect(); | .collect(); | ||||
| match last_header { | |||||
| Some(h) => { | |||||
| headers.push(proto::BlockHeader { | |||||
| raw_header: h.data.to_vec(), | |||||
| root, | |||||
| branch, | |||||
| }); | |||||
| } | |||||
| None => (), | |||||
| } | |||||
| Ok(proto::BlockHeaders { headers }) | Ok(proto::BlockHeaders { headers }) | ||||
| } | } | ||||
| } | } | ||||