diff --git a/chronik/bitcoinsuite-core/src/block/block_hash.rs b/chronik/bitcoinsuite-core/src/block/block_hash.rs new file mode 100644 index 000000000..9f69c2b49 --- /dev/null +++ b/chronik/bitcoinsuite-core/src/block/block_hash.rs @@ -0,0 +1,159 @@ +// Copyright (c) 2022 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +use crate::hash::{Hashed, HashedError, Sha256d}; + +/// Wraps a block hash's [`Sha256d`], to avoid mixing different kinds of hashes. +/// Block hashes are always represented with a big-endian hex string, but stored +/// in little-endian byteorder. +#[derive(Clone, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct BlockHash(Sha256d); + +impl std::fmt::Debug for BlockHash { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "BlockHash({})", self.0.hex_be()) + } +} + +impl std::fmt::Display for BlockHash { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.hex_be().fmt(f) + } +} + +impl BlockHash { + /// Returns the block hash bytes in little-endian byte order. + /// + /// ``` + /// # use bitcoinsuite_core::{block::BlockHash, hash::{Sha256d, Hashed}}; + /// # use hex_literal::hex; + /// let hash = hex!( + /// "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000" + /// ); + /// let block_hash = BlockHash::from(Sha256d(hash)); + /// assert_eq!(block_hash.to_bytes(), hash); + /// ``` + pub fn to_bytes(&self) -> [u8; 32] { + self.0.to_le_bytes() + } + + /// Returns the block hash as [`Vec`] in little-endian byte order. + /// + /// ``` + /// # use bitcoinsuite_core::{block::BlockHash, hash::{Sha256d, Hashed}}; + /// # use hex_literal::hex; + /// let hash = hex!( + /// "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000" + /// ); + /// let block_hash = BlockHash::from(Sha256d(hash)); + /// assert_eq!(block_hash.to_vec(), hash.to_vec()); + /// ``` + pub fn to_vec(&self) -> Vec { + self.to_bytes().to_vec() + } +} + +impl std::str::FromStr for BlockHash { + type Err = HashedError; + + fn from_str(s: &str) -> Result { + Ok(BlockHash(Sha256d::from_be_hex(s)?)) + } +} + +impl TryFrom<&'_ [u8]> for BlockHash { + type Error = HashedError; + + fn try_from(value: &'_ [u8]) -> Result { + Ok(BlockHash(Sha256d::from_le_slice(value)?)) + } +} + +impl From<[u8; 32]> for BlockHash { + fn from(array: [u8; 32]) -> Self { + BlockHash(Sha256d(array)) + } +} + +impl From for BlockHash { + fn from(hash: Sha256d) -> Self { + BlockHash(hash) + } +} + +impl AsRef<[u8]> for BlockHash { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +#[cfg(test)] +mod tests { + use hex_literal::hex; + + use crate::{ + block::BlockHash, + hash::{Hashed, HashedError, Sha256d}, + }; + + const GENESIS_HASH_HEX: &str = + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; + + fn genesis_hash() -> Sha256d { + let genesis_hash = hex!( + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" + ); + Sha256d::from_be_bytes(genesis_hash) + } + + #[test] + fn test_parse() -> Result<(), HashedError> { + let block_hash = GENESIS_HASH_HEX.parse::()?; + assert_eq!(block_hash, BlockHash::from(genesis_hash())); + Ok(()) + } + + #[test] + fn test_parse_fail() { + assert_eq!( + "abcd".parse::(), + Err(HashedError::InvalidLength { + expected: 32, + actual: 2, + }), + ); + } + + #[test] + fn test_try_from_slice() { + let hash = genesis_hash(); + let slice = hash.as_le_bytes().as_ref(); + let block_hash: BlockHash = slice.try_into().unwrap(); + assert_eq!(block_hash, BlockHash::from(hash)); + assert_eq!( + BlockHash::try_from(b"ab".as_ref()), + Err(HashedError::InvalidLength { + expected: 32, + actual: 2, + }), + ); + } + + #[test] + fn test_debug() { + assert_eq!( + format!("{:?}", BlockHash::from(genesis_hash())), + format!("BlockHash({})", GENESIS_HASH_HEX), + ); + } + + #[test] + fn test_display() -> Result<(), HashedError> { + assert_eq!( + BlockHash::from(genesis_hash()).to_string(), + GENESIS_HASH_HEX, + ); + Ok(()) + } +} diff --git a/chronik/bitcoinsuite-core/src/block/mod.rs b/chronik/bitcoinsuite-core/src/block/mod.rs new file mode 100644 index 000000000..18f846b54 --- /dev/null +++ b/chronik/bitcoinsuite-core/src/block/mod.rs @@ -0,0 +1,9 @@ +// Copyright (c) 2022 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +//! Module for data refering to blocks, e.g. [`BlockHash`]. + +mod block_hash; + +pub use self::block_hash::*; diff --git a/chronik/bitcoinsuite-core/src/lib.rs b/chronik/bitcoinsuite-core/src/lib.rs index 3aa268909..ee7868afb 100644 --- a/chronik/bitcoinsuite-core/src/lib.rs +++ b/chronik/bitcoinsuite-core/src/lib.rs @@ -1,37 +1,38 @@ // Copyright (c) 2022 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #![deny(unsafe_code)] #![warn( const_err, dead_code, elided_lifetimes_in_paths, explicit_outlives_requirements, improper_ctypes, keyword_idents, missing_debug_implementations, missing_docs, no_mangle_generic_items, non_ascii_idents, non_shorthand_field_patterns, noop_method_call, overflowing_literals, path_statements, patterns_in_fns_without_body, private_in_public, rust_2018_idioms, single_use_lifetimes, unconditional_recursion, unreachable_pub, unused_comparisons, unused, while_true )] //! Core primitives for dealing with Bitcoin-like chains. //! //! Note: This is a general purpose library, but has been optimized for the //! usage in Chronik, an indexer for Bitcoin ABC. +pub mod block; pub mod hash;