Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14864866
D17687.id52932.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
27 KB
Subscribers
None
D17687.id52932.diff
View Options
diff --git a/modules/bitcoinsuite-chronik-client/tests/block_and_blocks.rs b/modules/bitcoinsuite-chronik-client/tests/block_and_blocks.rs
new file mode 100644
--- /dev/null
+++ b/modules/bitcoinsuite-chronik-client/tests/block_and_blocks.rs
@@ -0,0 +1,605 @@
+// Copyright (c) 2025 The Bitcoin developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+use std::sync::{
+ atomic::{AtomicI32, Ordering},
+ Arc, Mutex,
+};
+
+use abc_rust_error::Result;
+use async_trait::async_trait;
+use bitcoinsuite_chronik_client::{
+ handler::{IpcHandler, IpcReader},
+ test_runner::{handle_test_info, spin_child_process},
+ ChronikClient, ChronikClientError,
+};
+use bitcoinsuite_core::{block::BlockHash, hash::Sha256d};
+use chronik_proto::proto::{Block, BlockInfo};
+use serde_json::Value;
+
+#[derive(Default)]
+struct BlockandBlocksIPC {
+ pub chronik_url: Mutex<String>,
+ pub counter: AtomicI32,
+}
+
+#[async_trait]
+impl IpcReader for BlockandBlocksIPC {
+ async fn on_rx(
+ &self,
+ handler: &mut IpcHandler,
+ json_data: Value,
+ ) -> Result<(), abc_rust_error::Report> {
+ const REGTEST_CHAIN_INIT_HEIGHT: i32 = 200;
+
+ // Step 0: Receive the Chronik URL
+ if let Some(test_info) = json_data.get("test_info") {
+ *self.chronik_url.lock().unwrap() = handle_test_info(&test_info)
+ .expect("Failed to extract chronik URL from test_info message");
+ }
+
+ if let Some(status) = json_data.get("status") {
+ if let Some(ready) = status.as_str() {
+ if ready == "ready" {
+ let chronik_url =
+ (*self.chronik_url.lock().unwrap()).clone();
+ match self.counter.load(Ordering::SeqCst) {
+ // Gives us the block and blocks
+ 0 | 1 => {
+ let block_from_height = get_block_by_height(
+ chronik_url.as_str(),
+ REGTEST_CHAIN_INIT_HEIGHT,
+ )
+ .await?;
+ assert_eq!(
+ REGTEST_CHAIN_INIT_HEIGHT,
+ block_from_height
+ .block_info
+ .as_ref()
+ .unwrap()
+ .height
+ );
+
+ // Get the same block by calling hash instead of
+ // height
+ let block_from_hash = get_block_by_hash(
+ chronik_url.as_str(),
+ block_from_height
+ .block_info
+ .as_ref()
+ .unwrap()
+ .hash
+ .clone(),
+ )
+ .await?;
+ // Verify it is the same
+ assert_eq!(block_from_hash, block_from_height);
+
+ // Gives us blocks
+ let last_three_blocks = get_blocks_in_range(
+ &chronik_url,
+ REGTEST_CHAIN_INIT_HEIGHT - 2,
+ REGTEST_CHAIN_INIT_HEIGHT,
+ )
+ .await?;
+ let last_three_blocks_length =
+ last_three_blocks.len();
+ assert_eq!(last_three_blocks_length, 3);
+
+ // Expect the last block to equal the most recent
+ // one called with block
+ assert_eq!(
+ block_from_hash.block_info.unwrap(),
+ last_three_blocks[2]
+ );
+
+ // Expect last_three_blocks to be in order
+ for i in 0..last_three_blocks.len() - 1 {
+ let this_block = &last_three_blocks[i];
+ let next_block = &last_three_blocks[i + 1];
+ assert_eq!(
+ this_block.hash,
+ next_block.prev_hash
+ );
+ assert_eq!(
+ this_block.height,
+ next_block.height - 1
+ );
+ }
+
+ // Throws expected error if we call blocks with
+ // startHeight > endHeight
+ let result = get_blocks_in_range(
+ &chronik_url,
+ REGTEST_CHAIN_INIT_HEIGHT,
+ REGTEST_CHAIN_INIT_HEIGHT - 2,
+ )
+ .await;
+
+ let err = result
+ .unwrap_err()
+ .downcast::<ChronikClientError>()?;
+
+ let status_code = match err {
+ ChronikClientError::ChronikError {
+ status_code: e,
+ ..
+ } => e,
+ _ => panic!(
+ "ChronikClientError should be raised, but
+ hasn't"
+ ),
+ };
+
+ assert_eq!(status_code, 400);
+ }
+
+ // Gives us the block at 10 higher
+ 2 => {
+ let block_from_height = get_block_by_height(
+ chronik_url.as_str(),
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ )
+ .await?;
+ assert_eq!(
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ block_from_height
+ .block_info
+ .as_ref()
+ .unwrap()
+ .height
+ );
+
+ // Get the same block by calling hash instead of
+ // height
+ let block_from_hash = get_block_by_hash(
+ chronik_url.as_str(),
+ block_from_height
+ .block_info
+ .as_ref()
+ .unwrap()
+ .hash
+ .clone(),
+ )
+ .await?;
+ // Verify it is the same
+ assert_eq!(block_from_hash, block_from_height);
+
+ // Gives us blocks
+ let last_three_blocks = get_blocks_in_range(
+ &chronik_url,
+ REGTEST_CHAIN_INIT_HEIGHT + 10 - 2,
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ )
+ .await?;
+ let last_three_blocks_length =
+ last_three_blocks.len();
+ assert_eq!(last_three_blocks_length, 3);
+
+ // Expect the last block to equal the most recent
+ // one called with block
+ assert_eq!(
+ block_from_hash.block_info.unwrap(),
+ last_three_blocks[2]
+ );
+
+ // Expect last_three_blocks to be in order
+ for i in 0..last_three_blocks.len() - 1 {
+ let this_block = &last_three_blocks[i];
+ let next_block = &last_three_blocks[i + 1];
+ assert_eq!(
+ this_block.hash,
+ next_block.prev_hash
+ );
+ assert_eq!(
+ this_block.height,
+ next_block.height - 1
+ );
+ }
+ }
+ // Gives us the block after parking the last block and
+ // throws expected error attempting to get parked block
+ 3 => {
+ let block_from_height = get_block_by_height(
+ chronik_url.as_str(),
+ REGTEST_CHAIN_INIT_HEIGHT + 9,
+ )
+ .await?;
+ assert_eq!(
+ REGTEST_CHAIN_INIT_HEIGHT + 9,
+ block_from_height
+ .block_info
+ .as_ref()
+ .unwrap()
+ .height
+ );
+
+ // Get the same block by calling hash instead of
+ // height
+ let block_from_hash = get_block_by_hash(
+ chronik_url.as_str(),
+ block_from_height
+ .block_info
+ .as_ref()
+ .unwrap()
+ .hash
+ .clone(),
+ )
+ .await?;
+ // Verify it is the same
+ assert_eq!(block_from_hash, block_from_height);
+
+ // Throws expected error if asked to fetch the
+ // parked block
+ let result = get_block_by_height(
+ chronik_url.as_str(),
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ )
+ .await;
+
+ let err = result
+ .unwrap_err()
+ .downcast::<ChronikClientError>()?;
+
+ let status_code = match err {
+ ChronikClientError::ChronikError {
+ status_code: e,
+ ..
+ } => e,
+ _ => panic!(
+ "ChronikClientError should be raised, but
+ hasn't"
+ ),
+ };
+
+ assert_eq!(status_code, 404);
+
+ // blocks does not throw error if asked for parked
+ // block, but also does not return it
+ let latest_blocks_available = get_blocks_in_range(
+ &chronik_url,
+ REGTEST_CHAIN_INIT_HEIGHT + 8,
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ )
+ .await?;
+ let latest_blocks_available_length =
+ latest_blocks_available.len();
+ assert_eq!(latest_blocks_available_length, 2);
+
+ // Expect latest_blocks_available to be in order
+ for i in 0..latest_blocks_available_length - 1 {
+ let this_block = &latest_blocks_available[i];
+ let next_block =
+ &latest_blocks_available[i + 1];
+ assert_eq!(
+ this_block.hash,
+ next_block.prev_hash
+ );
+ assert_eq!(
+ this_block.height,
+ next_block.height - 1
+ );
+ }
+
+ // We get an empty array if we ask for an
+ // unavailable block
+ let latest_block_available_by_blocks =
+ get_blocks_in_range(
+ &chronik_url,
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ )
+ .await?;
+ assert_eq!(latest_block_available_by_blocks, []);
+ }
+ // Gives us the block and blocks after unparking the
+ // last block
+ 4 => {
+ let block_from_height = get_block_by_height(
+ chronik_url.as_str(),
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ )
+ .await
+ .unwrap();
+ assert_eq!(
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ block_from_height
+ .block_info
+ .as_ref()
+ .unwrap()
+ .height
+ );
+
+ // Get the same block by calling hash instead of
+ // height
+ let block_from_hash = get_block_by_hash(
+ chronik_url.as_str(),
+ block_from_height
+ .block_info
+ .as_ref()
+ .unwrap()
+ .hash
+ .clone(),
+ )
+ .await?;
+
+ // Verify it is the same
+ assert_eq!(block_from_hash, block_from_height);
+
+ // Blocks gets the unparked block now
+ let latest_blocks_available = get_blocks_in_range(
+ &chronik_url,
+ REGTEST_CHAIN_INIT_HEIGHT + 8,
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ )
+ .await?;
+
+ let latest_blocks_available_length =
+ latest_blocks_available.len();
+ assert_eq!(latest_blocks_available_length, 3);
+
+ // Expect latest_blocks_available to be in order
+ for i in 0..latest_blocks_available_length - 1 {
+ let this_block = &latest_blocks_available[i];
+ let next_block =
+ &latest_blocks_available[i + 1];
+ assert_eq!(
+ this_block.hash,
+ next_block.prev_hash
+ );
+ assert_eq!(
+ this_block.height,
+ next_block.height - 1
+ );
+ }
+
+ // We get the now-unparked block
+ let latest_block_available_by_blocks =
+ get_blocks_in_range(
+ &chronik_url,
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ )
+ .await?;
+ assert_eq!(
+ vec![block_from_hash.block_info.unwrap()],
+ latest_block_available_by_blocks
+ );
+ }
+ // Gives us the block and blocks after invalidating the
+ // last block and throws
+ // expected error attempting to get
+ // invalidated block
+ 5 => {
+ let block_from_height = get_block_by_height(
+ chronik_url.as_str(),
+ REGTEST_CHAIN_INIT_HEIGHT + 9,
+ )
+ .await?;
+ assert_eq!(
+ REGTEST_CHAIN_INIT_HEIGHT + 9,
+ block_from_height
+ .block_info
+ .as_ref()
+ .unwrap()
+ .height
+ );
+
+ // Get the same block by calling hash instead of
+ // height
+ let block_from_hash = get_block_by_hash(
+ chronik_url.as_str(),
+ block_from_height
+ .block_info
+ .as_ref()
+ .unwrap()
+ .hash
+ .clone(),
+ )
+ .await?;
+ // Verify it is the same
+ assert_eq!(block_from_hash, block_from_height);
+
+ // Throws expected error if asked to fetch the
+ // invalidated block
+ let result = get_block_by_height(
+ chronik_url.as_str(),
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ )
+ .await;
+
+ let err = result
+ .unwrap_err()
+ .downcast::<ChronikClientError>()?;
+
+ let status_code = match err {
+ ChronikClientError::ChronikError {
+ status_code: e,
+ ..
+ } => e,
+ _ => panic!(
+ "ChronikClientError should be raised, but
+ hasn't"
+ ),
+ };
+
+ assert_eq!(status_code, 404);
+
+ // blocks does not throw error if asked for
+ // invalidated block, but also does not return it
+ let latest_blocks_available = get_blocks_in_range(
+ &chronik_url,
+ REGTEST_CHAIN_INIT_HEIGHT + 8,
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ )
+ .await?;
+ let latest_blocks_available_length =
+ latest_blocks_available.len();
+ assert_eq!(latest_blocks_available_length, 2);
+
+ // Expect latest_blocks_available to be in order
+ for i in 0..latest_blocks_available_length - 1 {
+ let this_block = &latest_blocks_available[i];
+ let next_block =
+ &latest_blocks_available[i + 1];
+ assert_eq!(
+ this_block.hash,
+ next_block.prev_hash
+ );
+ assert_eq!(
+ this_block.height,
+ next_block.height - 1
+ );
+ }
+
+ // We get an empty array if we ask for an
+ // unavailable
+ // (invalidated) block
+ let latest_block_available_by_blocks =
+ get_blocks_in_range(
+ &chronik_url,
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ )
+ .await?;
+ assert_eq!(latest_block_available_by_blocks, []);
+ }
+ // Gives us the block and blocks after reconsiderblock
+ // called on the last block
+ 6 => {
+ let block_from_height = get_block_by_height(
+ chronik_url.as_str(),
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ )
+ .await?;
+ assert_eq!(
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ block_from_height
+ .block_info
+ .as_ref()
+ .unwrap()
+ .height
+ );
+
+ // Get the same block by calling hash instead of
+ // height
+ let block_from_hash = get_block_by_hash(
+ chronik_url.as_str(),
+ block_from_height
+ .block_info
+ .as_ref()
+ .unwrap()
+ .hash
+ .clone(),
+ )
+ .await?;
+
+ // Verify it is the same
+ assert_eq!(block_from_hash, block_from_height);
+
+ // Blocks gets the reconsidered block now
+ let latest_blocks_available = get_blocks_in_range(
+ &chronik_url,
+ REGTEST_CHAIN_INIT_HEIGHT + 8,
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ )
+ .await?;
+ let latest_blocks_available_length =
+ latest_blocks_available.len();
+ assert_eq!(latest_blocks_available_length, 3);
+
+ // Expect latest_blocks_available to be in order
+ for i in 0..latest_blocks_available_length - 1 {
+ let this_block = &latest_blocks_available[i];
+ let next_block =
+ &latest_blocks_available[i + 1];
+ assert_eq!(
+ this_block.hash,
+ next_block.prev_hash
+ );
+ assert_eq!(
+ this_block.height,
+ next_block.height - 1
+ );
+ }
+
+ // We get the reconsidered block
+ let latest_block_available_by_blocks =
+ get_blocks_in_range(
+ &chronik_url,
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ REGTEST_CHAIN_INIT_HEIGHT + 10,
+ )
+ .await?;
+ assert_eq!(
+ vec![block_from_hash.block_info.unwrap()],
+ latest_block_available_by_blocks
+ );
+ }
+ _ => {
+ handler.send_message("stop").await?;
+ unreachable!(
+ "An unexpected ready message was sent from the
+ setup framework, causing the counter to
+ increment beyond a valid match arm."
+ );
+ }
+ }
+ self.counter.fetch_add(1, Ordering::SeqCst);
+ handler.send_message("next").await?;
+ return Ok(());
+ }
+ }
+ }
+
+ Ok(())
+ }
+}
+
+#[tokio::test]
+pub async fn block_and_blocks() -> Result<(), abc_rust_error::Report> {
+ let python_script = "block_and_blocks";
+
+ let ipc_reader = Arc::new(BlockandBlocksIPC::default());
+
+ spin_child_process(python_script, ipc_reader.clone()).await?;
+
+ Ok(())
+}
+
+pub async fn get_block_by_height(
+ chronik_url: &str,
+ block_height: i32,
+) -> Result<Block, abc_rust_error::Report> {
+ let client = ChronikClient::new(chronik_url.to_string())?;
+ client.block_by_height(block_height).await
+}
+
+pub async fn get_block_by_hash(
+ chronik_url: &str,
+ block_hash: Vec<u8>,
+) -> Result<Block, abc_rust_error::Report> {
+ let client = ChronikClient::new(chronik_url.to_string())?;
+
+ // Convert Vec<u8> to BlockHash
+ let block_hash =
+ BlockHash::try_from(block_hash.as_slice()).map_err(|_| {
+ abc_rust_error::Report::msg("Invalid block hash length")
+ })?;
+
+ // Convert BlockHash to Sha256d
+ let sha256d_hash = Sha256d(block_hash.to_bytes());
+
+ // Pass the Sha256d reference to the client
+ client.block_by_hash(&sha256d_hash).await
+}
+
+pub async fn get_blocks_in_range(
+ chronik_url: &str,
+ start_height: i32,
+ end_height: i32,
+) -> Result<Vec<BlockInfo>, abc_rust_error::Report> {
+ let client = ChronikClient::new(chronik_url.to_string())?;
+ client.blocks(start_height, end_height).await
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, May 20, 22:55 (4 h, 46 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5866103
Default Alt Text
D17687.id52932.diff (27 KB)
Attached To
D17687: [Chronik] Add integration test for block_and_blocks endpoint into bitcoinsuite-chronik-client
Event Timeline
Log In to Comment